美文网首页
AFNetworking源码学习之一-一个简单的POST请求

AFNetworking源码学习之一-一个简单的POST请求

作者: 会笑的Even | 来源:发表于2017-10-10 16:16 被阅读0次

    AFN作为iOS最流行的第三方库,确实为开发者省去了很多麻烦的代码,加入自己使用NSURLSeesion或者NSURLConnection来进行网络请求的话,会写很多头痛的代码,下面就对AFN的源码就行解读,话不多说,进入正题:

    1.我们先看AFN的目录结构:

    目录结构.png ,除去Support Files文件夹,由NSURLSession,Reachability,Security,Serialization,UIKit五个文件夹组成,其中NSURLSession文件夹是AFN请求的核心类,管理请求用的NURLSession类,有两个类组成:AFHTTPSessionManagerAFURLSessionManager,其中AFHTTPSessionManagerAFURLSessionManager的子类,AFHTTPSessionManager提供了GET,POST,DELETE等各种请求方式,但是具体做事情是其父类AFURLSessionManager,Reachability只有一个AFNetworkReachabilityManager类,负责网络状态的监听,Security负责https相关东西,内面有一个AFSecurityPolicy类设置网络安全策略的.Serialization负责网络请求和相应序列化,分别为AFURLRequestSerializationAFURLResponseSerialization响应序列化的,UIKit文件夹主要一些UI控件添加分类,比如UIImageView等.

    2.一个简单的post请求可简单写:

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager POST:URL parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
    
    } success:^(NSURLSessionDataTask * _Nonnull task, id responseObject) {
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    }];
    

    先到AFHTTPSessionManager类看[AFHTTPSessionManager manager]的代码如下:

    + (instancetype)manager {
    return [[[self class] alloc] initWithBaseURL:nil];
    }
    - (instancetype)initWithBaseURL:(NSURL *)url {
    return [self initWithBaseURL:url sessionConfiguration:nil];
    }
    
    - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    return [self initWithBaseURL:nil sessionConfiguration:configuration];
    }
    - (instancetype)initWithBaseURL:(NSURL *)url
    sessionConfiguration:(NSURLSessionConfiguration *)configuration
    {
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
    return nil;
    }
    // 如果有baseurl有值且不以@"/"结尾,就加上@"/"
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
    url = [url URLByAppendingPathComponent:@""];
    }
    self.baseURL = url;
    // 默认请求序列化方式为AFHTTPRequestSerializer
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    // 默认响应序列化方式为AFJSONResponseSerializer
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    
    return self;
    }
    

    我们可以看到主要做了以下几件事情:

    • 先利用父类的构造方法初始化,- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration.
    • 设置baseURL
    • 设置默认的请求和响应初始化方式
      那先看看父类中的-(instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration方法做哪些事情,代码如下:
    - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
    return nil;
    }
    if (!configuration) {
    configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }
    self.sessionConfiguration = configuration;
    // 代理回调的队列
    self.operationQueue = [[NSOperationQueue alloc] init];
    // 设置最大并发数为1
    self.operationQueue.maxConcurrentOperationCount = 1;
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    // 默认的安全策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
    #if !TARGET_OS_WATCH
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
    #endif
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
    // 利用NSLock保证线程安全
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
    for (NSURLSessionDataTask *task in dataTasks) {
    // 给每一个NSURLSessionDataTask对应一个AFURLSessionManagerTaskDelegate
    [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
    }
    for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
    // 给每一个NSURLSessionUploadTask对应一个AFURLSessionManagerTaskDelegate
    [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
    }
    for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
    // 给每一个NSURLSessionDownloadTask对应一个AFURLSessionManagerTaskDelegate
    [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
    }
    }];
    return self;
    }
    

    这段代码里[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { }],代码里有什么用呢?不是初始化self.session里task为空吗?,大神这样写是有原因的,原来是当后台线程回来时,不对其task重新初始化的话,会crash掉.

    我们再回到AFHTTPSessionManager中- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration:方法中序列化代码是这样的:

    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    

    AFHTTPRequestSerializer中代码如下:

    + (instancetype)serializer {
    return [[self alloc] init];
    }
    - (instancetype)init {
    self = [super init];
    if (!self) {
    return nil;
    }
    self.stringEncoding = NSUTF8StringEncoding;
    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
    NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
    // 设置header的语言,q为quality代表不同偏好程度,默认为1,大于0设置时才有效
    [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    float q = 1.0f - (idx * 0.1f);
    [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
    *stop = q <= 0.5f;
    }];
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
    NSString *userAgent = nil;
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
    // 消除了(use of GNU ?: conditional expression extension, omitting middle operand)的警告!
    #if TARGET_OS_IOS
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
    #elif TARGET_OS_WATCH
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
    #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
    #endif
    #pragma clang diagnostic pop
    if (userAgent) {
    if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
    NSMutableString *mutableUserAgent = [userAgent mutableCopy];
    if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
    userAgent = mutableUserAgent;
    }
    }
    // 设置header的User-Agent值
    [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    // 将AFHTTPRequestSerializerObservedKeyPaths()中的keypath,添加到KVO中
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
    if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
    [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
    }
    }
    return self;
    }
    
    

    我们知道NSMutableURLRequest是要设置header头的,这里拼接了Accept-Language和User-Agent两个参数保存在mutableHTTPRequestHeaders这个可变字典中.self.HTTPMethodsEncodingParametersInURI这个集合有GET,HEAD,DELETE三个参数,这个集合有啥用呢?因为这三种请求方式参数是拼接在URL后面的,其他请求方式是参数是设置在NSMutableURLRequest的HTTPBody中的,共给出了三种请求序列化方式:AFHTTPRequestSerializer,AFJSONRequestSerializer,AFPropertyListRequestSerializer,后两种继承于AFHTTPRequestSerializer,都遵守AFURLRequestSerialization协议.
    AFHTTPRequestSerializerObservedKeyPaths()是一个C语言函数:

    // 获取所观察属性的keypath
    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;
    }
    

    _AFHTTPRequestSerializerObservedKeyPaths有allowsCellularAccess,cachePolicy,HTTPShouldHandleCookies,HTTPShouldUsePipelining,networkServiceType,timeoutInterval这六个元素,然后这六个元素进行KVO监听:

    - (void)observeValueForKeyPath:(NSString *)keyPath
    ofObject:(__unused id)object
    change:(NSDictionary *)change
    context:(void *)context
    {
    if (context == AFHTTPRequestSerializerObserverContext) {
    if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
    [self.mutableObservedChangedKeyPaths removeObject:keyPath];
    } else {
    [self.mutableObservedChangedKeyPaths addObject:keyPath];
    }
    }
    }
    

    如果上面六个元素设置了不为null的值,就会被添加到self.mutableObservedChangedKeyPaths数组中,以在后面设置请求header里面的值.
    再回到:

    self.responseSerializer = [AFJSONResponseSerializer serializer];
    

    代码中,其实现如下:

    (instancetype)serializer {
    return [[self alloc] init];
    }
    
    - (instancetype)init {
    self = [super init];
    if (!self) {
    return nil;
    }
    // 编码方式
    self.stringEncoding = NSUTF8StringEncoding;
    // 正常响应的状态码的范围
    self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
    self.acceptableContentTypes = nil;
    
    return self;
    }
    

    上面代码设置正常响应的状态码为200-300,给出的响应方式有AFHTTPResponseSerializer,AFJSONResponseSerializer,AFXMLParserResponseSerializer,AFXMLDocumentResponseSerializer,AFPropertyListResponseSerializer,AFImageResponseSerializer,AFCompoundResponseSerializer后面六种方式继承于AFHTTPResponseSerializer,都遵守AFURLResponseSerialization协议.

    那我们回到之前发送请求的代码:

    - (NSURLSessionDataTask *)POST:(NSString *)URLString
                        parameters:(id)parameters
                          progress:(void (^)(NSProgress * _Nonnull))uploadProgress
                           success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                           failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
    {
        // 创建一个NSURLSessionDataTask
        NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
        [dataTask resume];
        return dataTask;
    }
    

    这段代码将请求方式,URL字符串,参数生成一个NSURLSessionDataTask对象,然后再开启这个dataTask任务.
    下面再看是怎么生成一个具体的dataTask任务的:

    - (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
    {
        NSError *serializationError = nil;
        
        // 创建一个NSMutableURLRequest对象
        NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
        if (serializationError) {
            if (failure) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
                // 如果设置了回调队列,回调结果就在回调队列里里执行,否则就在主队列执行
                dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                    failure(nil, serializationError);
                });
    #pragma clang diagnostic pop
            }
    
            return nil;
        }
    
        __block NSURLSessionDataTask *dataTask = nil;
    // 根据前面的request生成一个dataTask
        dataTask = [self dataTaskWithRequest:request
                              uploadProgress:uploadProgress
                            downloadProgress:downloadProgress
                           completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
            if (error) {
                if (failure) {
                    failure(dataTask, error);
                }
            } else {
                if (success) {
                    success(dataTask, responseObject);
                }
            }
        }];
    
        return dataTask;
    }
    

    这里主要做了两件事情:
    1.根据前面传来的参数利用自己的请求序列化生成一个NSMutableURLRequest对象
    2.根据前面生成NSMutableURLRequest对象生成一个NSURLSessionDataTask对象,并返回回去.

    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
    #pragma clang diagnostic pop
    

    这里是消除编译警告,消除(use of GNU ?: conditional expression extension, omitting middle operand)的警告.

    现在去看看是怎么利用请求序列化生成一个NSMutableURLRequest对象的,代码如下:

    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                     URLString:(NSString *)URLString
                                    parameters:(id)parameters
                                         error:(NSError *__autoreleasing *)error
    {
        // 参数断言,debug模式下缺少参数会直接崩溃
        NSParameterAssert(method);
        NSParameterAssert(URLString);
        NSURL *url = [NSURL URLWithString:URLString];
        NSParameterAssert(url);
        NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
        mutableRequest.HTTPMethod = method;
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
        mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    
        return mutableRequest;
    }
    

    AFHTTPRequestSerializerObservedKeyPaths()放着allowsCellularAccess,cachePolicy,HTTPShouldHandleCookies,HTTPShouldUsePipelining,networkServiceType,timeoutInterval六个请求序列化的属性,如果前面六个属性设置不为null的值,那么这个属性就会加到mutableObservedChangedKeyPaths这个集合中.然后将这些值设置在mutableRequest中.
    由于给出的请求序列化方式都遵守AFURLRequestSerialization协议,协议中有个

    - (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(nullable id)parameters
                                            error:(NSError * _Nullable __autoreleasing *)error
    

    方法.再看看其的具体实现:

    #pragma mark - AFURLRequestSerialization
    
    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
    
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
    
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
              // 遍历HTTPRequestHeaders字典,如果request中的field没有值,就设置值
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        NSString *query = nil;
        if (parameters) {
            // 自定义的queryString序列化方式
            if (self.queryStringSerialization) {
                NSError *serializationError;
                query = self.queryStringSerialization(request, parameters, &serializationError);
    
                if (serializationError) {
                    if (error) {
                        *error = serializationError;
                    }
    
                    return nil;
                }
            } else {
                // 默认的queryString序列化方式
                switch (self.queryStringSerializationStyle) {
                    case AFHTTPRequestQueryStringDefaultStyle:
                        query = AFQueryStringFromParameters(parameters);
                        break;
                }
            }
        }
    
        // 如果method为get,head,delete时,将query拼接到URL后面
        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 {
              // 如果query为空, query设置为空字符串
            if (!query) {
                query = @"";
            }
            // 设置headerField的Content-Type
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
            }
            // 将method为get,head,delete外的query设置在HTTPBody中
            [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        }
    
        return mutableRequest;
    }
    

    上面主要做了一下几件事情:

    • 给mutableRequest设置header头.
    • 根据queryString序列化方式生成一个query的字符串.
    • 然后根据请求的方式拼接query:如果是get,head,delete方式就直接拼接在url后面,其他方式设置在HTTPBody中.

    下面看看怎么生成queryString的:

    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);
    }
    
    NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
        NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
              // 创个一个排序的sortDescriptor
        NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
    
        if ([value isKindOfClass:[NSDictionary class]]) {
            NSDictionary *dictionary = value;
            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;
    }
    

    上面是三个c语言的函数,根据传进来的参数来生成一个queryString.
    然后递归调用AFQueryStringFromParameters方法,直到解析NSDictionary, NSArray, NSSet到以外的元素.
    AFQueryStringPair类的代码如下:

    
    @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;
    }
    // 对field和value进行编码
    - (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
    

    AFPercentEscapedStringFromString()方法代码如下:

    NSString * AFPercentEscapedStringFromString(NSString *string) {
        static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; //  根据RFC 3986不包含?和/两个字符
        static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
        NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
        [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
        // 这里为什么要一次性最大编码长度为50?因为在iOS8.1,8.2,系统有个bug,如果一次编码几百个中文字符,会导致会因为内存分配错误,而导致崩溃
        static NSUInteger const batchSize = 50;
    
        NSUInteger index = 0;
        NSMutableString *escaped = @"".mutableCopy;
    
        while (index < string.length) {
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wgnu"
            NSUInteger length = MIN(string.length - index, batchSize);
    #pragma GCC diagnostic pop
            NSRange range = NSMakeRange(index, length);
            // 避免多字节的字符,比如emoji等,被分隔开
            range = [string rangeOfComposedCharacterSequencesForRange:range];
    
            NSString *substring = [string substringWithRange:range];
            NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
            [escaped appendString:encoded];
    
            index += range.length;
        }
    
        return escaped;
    }
    

    以上几步总结起来就是下面的例子:

        @{
          @"a" : @"a",
          @"b": @{@"a": @"b", @"c": @"d"},
          @"c": @[@"a", @"b"],
          @"d": [NSSet setWithObjects:@"a", @"b", nil]
          }
        =>   a=a&b[a]=b&b[c]=d&c[]=a&c[]=b&d=a&d=b
    

    好了前面已经生成了一个NSMutableURLRequest对象,现在根据这个对象生成一个dataTask,代码如下:

    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                   uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                 downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
    
        __block NSURLSessionDataTask *dataTask = nil;
        // iOS8以下会并发创建的tasks,会出现相同的taskIdentifier,导致completionHandlers不正确
        url_session_manager_create_task_safely(^{
            dataTask = [self.session dataTaskWithRequest:request];
        });
        // 将AFURLSessionManagerTaskDelegate对象和一个dataTask对象一一对应.
        [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    
        return dataTask;
    }
    

    这里用了一个了安全的处理方法,由于苹果iOS8创建dataTask会并发的创建,会出现相同的taskIdentifier,而一个taskIdentifier对应一个回调的completionHandlers所以会出现错误.
    根据前面的代码我们已经生成了一个dataTask,并将这个任务resume了.其实AFN中还做了当一个dataTask resume或者suspend之后,给外界发出了一个通知,供外界监听.所以我们不仅要resume和suspend的系统的实现,还要给外界发出通知,供外界监听.所以我们需要对dataTask类的resume和suspend进行方法调配(method swizzle),但是NSURLSessionTasks是一个类簇,我们实际创建任务不是这个类,这需要几条是需要确定的:

              // NSURLSessionTasks是在类簇实现的,意味着API中请求的类型和你得到类型并不一致
             // 调用[NSURLSessionTask class]并不管用,你需要询问NSURLSession是哪个类创建了这个对象
             // 在iOS7中 localDataTask 是一个 __NSCFLocalDataTask类型,继承于__NSCFLocalSessionTask,而__NSCFLocalSessionTask继承于__NSCFURLSessionTask
             // 在iOS8中,localDataTask是一个__NSCFLocalDataTask类型,继承于__NSCFLocalSessionTask,而__NSCFLocalSessionTask继承于NSURLSessionTask
             // 在iOS7中,__NSCFLocalSessionTask和__NSCFURLSessionTask是唯一的两个类实现了resume和suspend两个方法,__NSCFLocalSessionTask不会调用父类的实现,意味着这两个类需要被swizzled
             // 在iOS8中,NSURLSessionTask 是唯一的类实现resume和suspend方法,这意味着这一唯一的一个类需要被swizzled
             // 因为NSURLSessionTask在每一个iOS版本不涉及到类的层级结构,所以给一个dummy class添加swizzled methods来管理他们
            // 一些假设:
             // resume或者suspend方法没有调用super,如果这会在将来的iOS版本中变化,我们需要处理它
             // 没有后台任务类复写resume或者suspend
             // 当前的解决方法
             1.获取一个__NSCFLocalDataTask实例,通过NSURLSession的一个data task来获取
             2.通过af_resume指针指向原来的实现
             3.检查当前的类是否实现resume方法,如果实现,继续第四部
             4.获取当前的类的父类
             5.获取当前类的的resume的当前实现的指针
             6.获取当前类的父类resume的当前实现的指针
             7.如果当前的类resume不等于父类的resume实现,并且当前resume的实现不同于af_resume的实现,然后就行swizzle the methods
             8.设置当前的父类为自己,并重复3-8
    

    所以这里借助了一个_AFURLSessionTaskSwizzling这样的一个dummy class来进行method swizzled.具体实现如下:

    static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
        Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    
    static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
        return class_addMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
    }
    
    static NSString * const AFNSURLSessionTaskDidResumeNotification  = @"com.alamofire.networking.nsurlsessiontask.resume";
    static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofire.networking.nsurlsessiontask.suspend";
    
    @interface _AFURLSessionTaskSwizzling : NSObject
    
    @end
    
    @implementation _AFURLSessionTaskSwizzling
    
    + (void)load {
            // 返回一个预设配置,没有持久性存储的缓存,Cookie或证书。这对于实现像"秘密浏览"功能的功能来说,是很理想的。
            NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
            NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wnonnull"
            NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
    #pragma clang diagnostic pop
            IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
            Class currentClass = [localDataTask class];
            
            while (class_getInstanceMethod(currentClass, @selector(resume))) {
                Class superClass = [currentClass superclass];
                IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
                IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
                if (classResumeIMP != superclassResumeIMP &&
                    originalAFResumeIMP != classResumeIMP) {
                    [self swizzleResumeAndSuspendMethodForClass:currentClass];
                }
                currentClass = [currentClass superclass];
            }
            
            [localDataTask cancel];
            [session finishTasksAndInvalidate];
        }
    }
    
    + (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
        Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
        Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
    
        if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
            af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
        }
    
        if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
            af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
        }
    }
    
    - (NSURLSessionTaskState)state {
        NSAssert(NO, @"State method should never be called in the actual dummy class");
        return NSURLSessionTaskStateCanceling;
    }
    // 在原来的resume实现的基础上,发送了AFNSURLSessionTaskDidResumeNotification的通知
    - (void)af_resume {
        NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
        NSURLSessionTaskState state = [self state];
        [self af_resume];
        
        if (state != NSURLSessionTaskStateRunning) {
            [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
        }
    }
    // 在原来的suspend的实现的基础上,发送了一个AFNSURLSessionTaskDidSuspendNotification通知
    - (void)af_suspend {
        NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
        NSURLSessionTaskState state = [self state];
        [self af_suspend];
        
        if (state != NSURLSessionTaskStateSuspended) {
            [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
        }
    }
    @end
    

    这里发出的通知,给外界提供了监听,比如AFN中的为UIKit添加的分类,都需要监听这两个通知.前面的dataTask创建好了,并恢复了任务,现在就看看回调的代码:

    #pragma mark - NSURLSessionTaskDelegate
    
    - (void)URLSession:(__unused NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didCompleteWithError:(NSError *)error
    {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
        __strong AFURLSessionManager *manager = self.manager;
    
        __block id responseObject = nil;
    
        __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
        userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
    
        //Performance Improvement from #2672
        NSData *data = nil;
        if (self.mutableData) {
            data = [self.mutableData copy];
            // 解除self.mutableData的引用,释放一些内存
            self.mutableData = nil;
        }
    
        if (self.downloadFileURL) {
            userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
        } else if (data) {
            userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
        }
        // 如果错误
        if (error) {
            
            userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
    
            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, error);
                }
    
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        } else {
            // 如果没有错误,进行响应序列化返回一个responseObject
            dispatch_async(url_session_manager_processing_queue(), ^{
                NSError *serializationError = nil;
                responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
    
                if (self.downloadFileURL) {
                    responseObject = self.downloadFileURL;
                }
    
                if (responseObject) {
                    userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
                }
    
                if (serializationError) {
                    userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
                }
    
                dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                    if (self.completionHandler) {
                        self.completionHandler(task.response, responseObject, serializationError);
                    }
    
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                    });
                });
            });
        }
    #pragma clang diagnostic pop
    }
    

    上面的代码如果有错误,就直接返回,否则就行响应序列化,返回responseObject,下面去响应序列化的代码如下:

    #pragma mark - AFURLResponseSerialization
    
    - (id)responseObjectForResponse:(NSURLResponse *)response
                               data:(NSData *)data
                              error:(NSError *__autoreleasing *)error
    {
        // 验证response是否合法
        if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
            if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
                return nil;
            }
        }
    
        id responseObject = nil;
        NSError *serializationError = 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]];
        if (data.length > 0 && !isSpace) {
            responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
        } else {
            return nil;
        }
        // 移除值为nil或者null的key
        if (self.removesKeysWithNullValues && responseObject) {
            responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
        }
    
        if (error) {
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
    
        return responseObject;
    }
    
    #pragma mark -
    
    - (BOOL)validateResponse:(NSHTTPURLResponse *)response
                        data:(NSData *)data
                       error:(NSError * __autoreleasing *)error
    {
        BOOL responseIsValid = YES;
        NSError *validationError = nil;
    
        if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
            if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
                !([response MIMEType] == nil && [data length] == 0)) {
                // 如果是AFJSONResponseSerializer 的话,接受的contentTypes只有@"application/json", @"text/json", @"text/javascript"三种,如果返回的contentType在接受的类型中,直接抛出错误;
                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;
            }
            // 如果返回的状态码不在(200-300)之间,抛出错误
            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;
    }
    

    上面的代码主要做了两件事:
    1.验证response是否合法,接受的类型是否匹配,状态码是否在可接受的范围.
    2.进行json序列化

    通过前面的介绍,梳理了一个post请求,AFN帮我们做了那些事,大神的编程思想确实牛逼,只有不断阅读大神的源码,才能对编程更深的认识.

    相关文章

      网友评论

          本文标题:AFNetworking源码学习之一-一个简单的POST请求

          本文链接:https://www.haomeiwen.com/subject/kvjgextx.html