首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS AFNetworking 源码阅读三

iOS AFNetworking 源码阅读三

作者头像
赵哥窟
发布2018-12-17 10:56:56
8330
发布2018-12-17 10:56:56
举报
文章被收录于专栏:日常技术分享日常技术分享

接下来我们来补充之前AFURLResponseSerialization这一块是如何解析数据的

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

/**
 The response object decoded from the data associated with a specified response.

 @param response The response to be processed.
 @param data The response data to be decoded.
 @param error The error that occurred while attempting to decode the response data.

 @return The object decoded from the specified response data.
 */
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end

这实际是一个协议方法。而后面的解析类都是遵守这个协议方法,去做数据解析

AFHTTPResponseSerializer:
- (id)responseObjectForResponse:(NSHTTPURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }

    return [[NSXMLParser alloc] initWithData:data];
}

方法调用了一个另外的方法之后,就把data返回来了

// 判断是不是可接受类型和可接受code,不是则填充error
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    // response是否合法标识
    BOOL responseIsValid = YES;
    // 验证的error
    NSError *validationError = nil;
    // 如果存在且是NSHTTPURLResponse
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        //主要判断自己能接受的数据类型和response的数据类型是否匹配,
        //如果有接受数据类型,如果不匹配response,而且响应类型不为空,数据长度不为0
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
            !([response MIMEType] == nil && [data length] == 0)) {
            //进入If块说明解析数据肯定是失败的,这时候要把解析错误信息放到error里。
            //如果数据长度大于0,而且有响应url
            if ([data length] > 0 && [response URL]) {
                // 错误信息字典,填充一些错误信息
                NSMutableDictionary *mutableUserInfo = [@{
                                                          NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
                                                          NSURLErrorFailingURLErrorKey:[response URL],
                                                          AFNetworkingOperationFailingURLResponseErrorKey: response,
                                                        } mutableCopy];
                if (data) {
                    mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
                }
                //生成错误
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }
            //返回标识
            responseIsValid = NO;
        }
        
        //判断自己可接受的状态吗
        //如果和response的状态码不匹配,则进入if块
        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
            //填写错误信息字典
            NSMutableDictionary *mutableUserInfo = [@{
                                               NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
                                               NSURLErrorFailingURLErrorKey:[response URL],
                                               AFNetworkingOperationFailingURLResponseErrorKey: response,
                                       } mutableCopy];

            if (data) {
                mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
            }
             //生成错误
            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
            //返回标识
            responseIsValid = NO;
        }
    }
    //给我们传过来的错误指针赋值
    if (error && !responseIsValid) {
        *error = validationError;
    }
    //返回是否错误标识
    return responseIsValid;
}

●简单来说,这个方法就是来判断返回数据与咱们使用的解析器是否匹配,需要解析的状态码是否匹配。如果错误,则填充错误信息,并且返回NO,否则返回YES,错误信息为nil。

●其中里面出现了两个属性值,一个acceptableContentTypes,一个acceptableStatusCodes,两者在初始化的时候有给默认值,我们也可以去自定义,但是如果给acceptableContentTypes定义了不匹配的类型,那么数据仍旧会解析错误。

●而AFHTTPResponseSerializer仅仅是调用验证方法,然后就返回了data。

AFJSONResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //先判断是不是可接受类型和可接受code
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        //error为空,或者有错误,去函数里判断。
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            //返回空
            return nil;
        }
    }

    // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
    // See https://github.com/rails/rails/issues/1742
    
    //如果数据为空
    BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
    //空则返回nil
    if (data.length == 0 || isSpace) {
        return nil;
    }
    
    NSError *serializationError = nil;
    // 不为空解析Jason
    id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];

    if (!responseObject)
    {
        // 拿着json解析的error去填充错误信息
        if (error) {
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
        return nil;
    }
    //判断是否需要移除Null值
    if (self.removesKeysWithNullValues) {
        return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }

    //返回解析结果
    return responseObject;
}

接下来我们接着看调用的第一个方法

// 判断是不是我们自己之前生成的错误信息,是的话返回YES
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    // 判断错误域名和传过来的域名是否一致,错误code是否一致
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
    }
    //如果userInfo的NSUnderlyingErrorKey有值,则在判断一次。
    else if (error.userInfo[NSUnderlyingErrorKey]) {
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

这里可以注意,我们这里传过去的code和domain两个参数分别为NSURLErrorCannotDecodeContentData、AFURLResponseSerializationErrorDomain,这两个参数是我们之前判断response可接受类型和code时候自己去生成错误的时候填写的。

调用第二个方法

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    //分数组和字典
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        //生成一个数组
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            //调用自己
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }
        //看我们解析类型是mutable还是非muatable,返回mutableArray或者array
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
             //value空则移除
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                //如果数组还是去调用自己
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}

方法主要还是通过递归的形式实现。比较简单。

第三个

static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    if (!error) {
        return underlyingError;
    }

    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }

    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

方法主要是把json解析的错误,赋值给我们需要返回给用户的error上。比较简单

至此,AFJSONResponseSerializer就讲完了

现在我们回到一开始初始化的这行代码上:

self.operationQueue.maxConcurrentOperationCount = 1;

首先我们要明确一个概念,这里的并发数仅仅是回调代理的线程并发数。而不是请求网络的线程并发数。请求网络是由NSURLSession来做的,它内部维护了一个线程池,用来做网络请求。它调度线程,基于底层的CFSocket去发送请求和接收数据。这些线程是并发的。

明确了这个概念之后,我们来梳理一下AF的整个流程和线程的关系: ● 一开始初始化sessionManager的时候,一般都是在主线程。 ● 然后我们调用get或者post等去请求数据,接着会进行request拼接,AF代理的字典映射,progressKVO添加等等,到NSUrlSessionresume之前这些准备工作,仍旧是在主线程中的。 ● 然后我们调用NSUrlSessionresume,接着就跑到NSUrlSession内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。 ● 紧接着数据请求完成后,回调回来在我们一开始生成的并发数为1的NSOperationQueue中,这个时候会是多线程串行的回调回来的。 ● 然后我们到返回数据解析那一块,我们自己又创建了并发的多线程,去对这些数据进行了各种类型的解析。 ●最后我们如果有自定义的completionQueue,则在自定义的queue中回调回来,也就是分线程回调回来,否则就是主队列,主线程中回调结束。

最后我们来解释解释为什么回调Queue要设置并发数为1:

我认为AF这么做有以下两点原因: 1.众所周知,AF2.x所有的回调是在一条线程,这条线程是AF的常驻线程,而这一条线程正是AF调度request的思想精髓所在,所以第一个目的就是为了和之前版本保持一致。 2.因为跟代理相关的一些操作AF都使用了NSLock。所以就算Queue的并发数设置为n,因为多线程回调,锁的等待,导致所提升的程序速度也并不明显。反而多task回调导致的多线程并发,平白浪费了部分性能。 而设置Queue的并发数为1,(注:这里虽然回调Queue的并发数为1,仍然会有不止一条线程,但是因为是串行回调,所以同一时间,只会有一条线程在操作AFUrlSessionManager的那些方法。)至少回调的事件,是不需要多线程并发的。回调没有了NSLock的等待时间,所以对时间并没有多大的影响。(注:但是还是会有多线程的操作的,因为设置刚开始调起请求的时候,是在主线程的,而回调则是串行分线程。)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.11.23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AFHTTPResponseSerializer:
  • AFJSONResponseSerializer:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档