AFNetworking 3.x源码赏析(一)

  AFNetworking是Objective-C最经典的网络请求库了,目前GitHub star 数量为31.1k。解读赏析经典源码是提高自身技术,加强对技术深度理解的一条快捷之路。本文旨在梳理AFNetworking网络是怎么开始加载网络,到网络返回之后的数据处理等一系列操作。

一、网络发起前对数据的处理

  AFNetworking 3.x发起http网络请求,主要使用AFHTTPSessionManager,关于下载和上传,后面会说道。
  AFHTTPSessionManager中好几个方法都是是对下面这个方法的封装,包扩HTTPMethod为GET、HEAD、POST、PUT、PATCH、DELETE的几个方法。

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                   URLString:(NSString *)URLString
                                  parameters:(id)parameters
                              uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                            downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                     success:(void (^)(NSURLSessionDataTask *, id))success
                                     failure:(void (^)(NSURLSessionDataTask *, NSError *))failure

而发起请求是使用的 NSURLSessionDataTask,在发起请求之前,首先生成对应的NSMutableURLRequest

1. 设置URL

  一般情况,初始化AFHTTPSessionManager时,要设置baseURL,设置过baseURL后,在指定URLString时直接使用URL path就可以了,并且在使用非baseURL的完整链接时不影响正确性。根据NSMutableURLRequest的生成来看

    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

baseURL是通过URLWithString:relativeToURL:方法来使用的,下面是AFNetworking关于此方法的官方示例。

NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
[NSURL URLWithString:@"foo" relativeToURL:baseURL];                  // http://example.com/v1/foo
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL];          // http://example.com/v1/foo?bar=baz
[NSURL URLWithString:@"/foo" relativeToURL:baseURL];                 // http://example.com/foo
[NSURL URLWithString:@"foo/" relativeToURL:baseURL];                 // http://example.com/v1/foo
[NSURL URLWithString:@"/foo/" relativeToURL:baseURL];                // http://example.com/foo/
[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
2. AFHTTPRequestSerializer

  生成NSMutableURLRequest的主要是AFHTTPRequestSerializer类。此类是网络发起前对数据的处理核心类。AFHTTPRequestSerializer在生成NSMutableURLRequest时,有如下几个步骤:

2.1 为每个请求添加映射属性

   NSMutableURLRequest有allowsCellularAccesscachePolicyHTTPShouldHandleCookiesHTTPShouldUsePipeliningnetworkServiceTypetimeoutInterval六个属性,被映射到AFHTTPRequestSerializer中,在AFHTTPRequestSerializer类里设置这六个属性,生成NSMutableURLRequest时,会被逐个复制到NSMutableURLRequest中。例如AFHTTPRequestSerializer类中,在allowsCellularAccess的注释里,作者这么写:

Whether created requests can use the device’s cellular radio (if present). YES by default.
@see NSMutableURLRequest -setAllowsCellularAccess:

AFHTTPRequestSerializer类的allowsCellularAccess,可直接去参见NSMutableURLRequest 的-setAllowsCellularAccess:,其他属性类似。作者写设置这几个属性的方式也是相当经典的写法。首先,在AFHTTPRequestSerializer中设置一个单例数组,用来存储映射属性,然后初始化AFHTTPRequestSerializer时,用观察者观察数组中每一个属性的值的变化,当设置单例数组中任何一个属性后,AFHTTPRequestSerializer就会把该属性的方法名添加到mutableObservedChangedKeyPaths中,最后在生成mutableRequest时,逐个设置到mutableRequest。如下:

for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
    if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
        [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
    }
}

//AFHTTPRequestSerializerObservedKeyPaths()
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
    static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _AFHTTPRequestSerializerObservedKeyPaths = @[
            NSStringFromSelector(@selector(allowsCellularAccess)),
            NSStringFromSelector(@selector(cachePolicy)),
            NSStringFromSelector(@selector(HTTPShouldHandleCookies)),
            NSStringFromSelector(@selector(HTTPShouldUsePipelining)), 
            NSStringFromSelector(@selector(networkServiceType)), 
            NSStringFromSelector(@selector(timeoutInterval))];
    });

  return _AFHTTPRequestSerializerObservedKeyPaths;
}

以下是这几个属性的意思
allowsCellularAccess 设置是否允许蜂窝网络访问,默认YES
cachePolicy 缓存行为 NSURLRequestCachePolicy类

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    //对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。
    NSURLRequestUseProtocolCachePolicy = 0,
    //数据需要从原始地址加载。不使用现有缓存。
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    //不仅忽略本地缓存,同时也忽略代理服务器或其他中间介质目前已有的、协议允许的缓存。
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented
    NSURLRequestReloadIgnoringCacheData =     NSURLRequestReloadIgnoringLocalCacheData,
  
    //无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据。
    NSURLRequestReturnCacheDataElseLoad = 2,
    //无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,
    //那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
    NSURLRequestReturnCacheDataDontLoad = 3,
    //从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。
    NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
};

HTTPShouldHandleCookies 是否使用NSURLRequest发送合适的存储cookie,默认YES
HTTPShouldUsePipelining

通常默认情况下请求和响应是顺序的,也就是说请求–>得到响应后,再请求。 如果将HTTPShouldUsePipelining设置为YES, 则允许不必等到response, 就可以再次请求。这个会很大的提高网络请求的效率,但是也可能会出问题。
因为客户端无法正确的匹配请求与响应,所以这依赖于服务器必须保证,响应的顺序与客户端请求的顺序一致。如果服务器不能保证这一点,那可能导致响应和请求混乱。

networkServiceType 网络请求的服务类型,是个枚举。在Voip,请求视频,后台下载,语音请求的时候可以特殊设置。
timeoutInterval 超时时间间隔,默认是60秒。

2.2 设置http header

  在NSURLRequest 设置http header时,使用- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field,这个方法,在AFHTTPRequestSerializer中有映射。在调用这个方法后,Header被存在mutableHTTPRequestHeaders字典中,AFHTTPRequestSerializer生成NSMutableURLRequest时,会逐个设置到Request中。代码如下:

//HTTPRequestHeaders即由mutableHTTPRequestHeaders生成
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
    if (![request valueForHTTPHeaderField:field]) {
        [mutableRequest setValue:value forHTTPHeaderField:field];
    }
}];
2.3 请求参数的序列化

  AFHTTPRequestSerializer支持参数自定义序列化只需要实现queryStringSerialization属性,这是AFQueryStringSerializationBlock。
  默认情况下,如果传递参数字典后会使用NSString * AFQueryStringFromParameters(NSDictionary *parameters)方法来拼装成QueryString。

if (parameters) {
    if (self.queryStringSerialization) {//自定义参数序列化
        NSError *serializationError;
        query = self.queryStringSerialization(request, parameters, &serializationError);

        if (serializationError) {
            if (error) {
                *error = serializationError;
            }

            return nil;
        }
    } else {//默认参数序列化
        switch (self.queryStringSerializationStyle) {
            case AFHTTPRequestQueryStringDefaultStyle:
                query = AFQueryStringFromParameters(parameters);
                break;
        }
    }
}

AFQueryStringFromParameters()方法参数序列化
主要使用NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)方法得到AFQueryStringPair的数组,然后利用For循环进行拼接。AFQueryStringFromParameters拼接“&”,AFQueryStringPair的URLEncodedStringValue用来拼接“=”

NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    return [mutablePairs componentsJoinedByString:@"&"];
}

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
//此方法是个单例,最终把参数字典,转化为AFQueryStringPair类
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    } else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    } else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

AFQueryStringPair 用来存储key-value,然后使用URLEncodedStringValue方法拼接“=”,而AFPercentEscapedStringFromString()是对参数里的字段进行编码,可参见RFC3986文档。这个博客里也有介绍。

@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end

@implementation AFQueryStringPair

- (instancetype)initWithField:(id)field value:(id)value {
    self = [super init];
    if (!self) {
        return nil;
    }
    self.field = field;
    self.value = value;
    return self;
}

- (NSString *)URLEncodedStringValue {
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        return AFPercentEscapedStringFromString([self.field description]);
    } else {
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
    }
}
@end
2.4 生成NSMutableURLRequest
//HTTPMethodsEncodingParametersInURI用来存储请求方法
//包括@"GET", @"HEAD", @"DELETE",当请求是这三种时,
//直接将参数query拼接在链接里。
//不是这三种,则将参数query转化为NSData设置为HTTPBody。编码方式默认为NSUTF8StringEncoding
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
    if (query && query.length > 0) {
        mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
    }
} else {
    // #2864: an empty string is a valid x-www-form-urlencoded payload
    if (!query) {
        query = @"";
    }
    if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
        [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    }
    [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}

参考:
1.NSURLRequestCachePolicy
2.HTTPShouldUsePipelining解释
3.RFC3986之URL编码与解码、AFPercentEscapedStringFromString

你可能感兴趣的:(AFNetworking 3.x源码赏析(一))