美文网首页
AFNetworking源码阅读(二)

AFNetworking源码阅读(二)

作者: HoooChan | 来源:发表于2018-11-19 18:14 被阅读15次

    AFURLRequestSerialization

    AF一共实现了三种RequestSerialization:AFHTTPRequestSerializer、AFJSONRequestSerializer、AFPropertyListRequestSerializer。

    AFHTTPRequestSerializer实现了AFURLRequestSerialization协议,协议里面只有一个方法:

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

    AFJSONRequestSerializer和AFPropertyListRequestSerializer都继承自AFHTTPRequestSerializer。主要的差别是Content-Type的类型。

    AFHTTPSessionManager里的requestSerializer:

    @property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
    

    在发起请求的时候,会通过self.requestWidthMethod来生成request:

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

    来到self.requestSerializer的requestWithMethod方法:

    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                     URLString:(NSString *)URLString
                                    parameters:(id)parameters
                                         error:(NSError *__autoreleasing *)error
    {
        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;
    }
    

    主要做了三件事:
    1、用url创建NSMutableURLRequest;
    2、设置NSMutableURLRequest的相关属性;
    3、序列化NSMutableURLRequest。

    在第二步中,AF用一个单例数组来保存NSMutableURLRequest一些属性的名称:

    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;
    }
    

    而AFHTTPRequestSerializer自己也实现了这些属性,在初始化的时候对这些属性进行监听:

        static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerObserverContext;
    
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
                [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
            }
        }
    

    当设置了这些属性的值时,会把它们存到一个数组里, 当创建请求的时候再把这些属性设置到NSMutableURLRequest里去,避免了一个一个地去判断每个属性。

    - (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];
            }
        }
    }
    
        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];
            }
        }
    

    第二步的源码:

    - (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) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        NSString *query = nil;
        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;
                }
            }
        }
    
        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]];
        }
    
        return mutableRequest;
    }
    

    1、拷贝传进来的NSURLRequest为NSMutableURLRequest;
    2、设置头部;
    3、拼接queryString;
    4、设置queryString;

    拼接queryString的时候先判断queryStringSerialization是否为空,queryStringSerialization就是AFQueryStringSerializationBlock,可以自定义怎么拼接参数。如果为空则进入AFQueryStringFromParameters方法:

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

    设置queryString的时候,会根据HTTPMethodsEncodingParametersInURI这个数组来判断应该把参数拼在地址后面还是设置到Body当中。

    如果我们要发起一个Post请求,我们就要对我们要发送的数据进行编码。常见的三种编码的方式:

    • application/x-www-form-urlencoded(默认): 就是常见的在Url后面直接拼接Query字符串;
    • multipart/form-data : 用来发送文件;
    • application/json : 用来告诉服务端消息主体是序列化后的 JSON 字符串。

    至此一个NSMutableURLRequest就创建完成了。

    除了requestWithMethod创造NSMutableURLRequest之外,AFHTTPRequestSerializer还有另外两个创建Request的方法:

    - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                                  URLString:(NSString *)URLString
                                                 parameters:(NSDictionary *)parameters
                                  constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                      error:(NSError *__autoreleasing *)error
    
    - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                                 writingStreamContentsToFile:(NSURL *)fileURL
                                           completionHandler:(void (^)(NSError *error))handler
    

    先看multipartFormRequestWithMethod:

    - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                                  URLString:(NSString *)URLString
                                                 parameters:(NSDictionary *)parameters
                                  constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                      error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(method);
        NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
    
        NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
    
        __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
    
        if (parameters) {
            for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
                NSData *data = nil;
                if ([pair.value isKindOfClass:[NSData class]]) {
                    data = pair.value;
                } else if ([pair.value isEqual:[NSNull null]]) {
                    data = [NSData data];
                } else {
                    data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
                }
    
                if (data) {
                    [formData appendPartWithFormData:data name:[pair.field description]];
                }
            }
        }
    
        if (block) {
            block(formData);
        }
    
        return [formData requestByFinalizingMultipartFormData];
    }
    

    1、根据method和ur创建一个NSMutableURLRequest;
    2、创建AFStreamingMultipartFormData formData;
    3、把参数设置到formData当中;
    4、做最后的序列化。

    先看AFStreamingMultipartFormData:@interface AFStreamingMultipartFormData : NSObject <AFMultipartFormData>, 实现了AFMultipartFormData协议。

    AFMultipartFormData里面的方法:

    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                            error:(NSError * _Nullable __autoreleasing *)error;
    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                            error:(NSError * _Nullable __autoreleasing *)error;
    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                         mimeType:(NSString *)mimeType
                            error:(NSError * _Nullable __autoreleasing *)error;
    - (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
                                 name:(NSString *)name
                             fileName:(NSString *)fileName
                               length:(int64_t)length
                             mimeType:(NSString *)mimeType;
    - (void)appendPartWithFileData:(NSData *)data
                              name:(NSString *)name
                          fileName:(NSString *)fileName
                          mimeType:(NSString *)mimeType;
    - (void)appendPartWithFormData:(NSData *)data
                              name:(NSString *)name;
    - (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
                             body:(NSData *)body;
    - (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                      delay:(NSTimeInterval)delay;
    

    可以看到大部分是添加文件数据和头部的方法。

    看看AFStreamingMultipartFormData的实现:

    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                            error:(NSError * __autoreleasing *)error
    {
        NSParameterAssert(fileURL);
        NSParameterAssert(name);
    
        NSString *fileName = [fileURL lastPathComponent];
        NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);
    
        return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error];
    }
    

    通过AFContentTypeForPathExtension方法来获取mimeType:

    static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
        NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
        NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
        if (!contentType) {
            return @"application/octet-stream";
        } else {
            return contentType;
        }
    }
    

    然后继续:

    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                         mimeType:(NSString *)mimeType
                            error:(NSError * __autoreleasing *)error
    {
        NSParameterAssert(fileURL);
        NSParameterAssert(name);
        NSParameterAssert(fileName);
        NSParameterAssert(mimeType);
    
        if (![fileURL isFileURL]) {
            NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)};
            if (error) {
                *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
            }
    
            return NO;
        } else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) {
            NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)};
            if (error) {
                *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
            }
    
            return NO;
        }
    
        NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error];
        if (!fileAttributes) {
            return NO;
        }
    
        NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
        [mutableHeaders setValue:mimeType forKey:@"Content-Type"];
    
        AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
        bodyPart.stringEncoding = self.stringEncoding;
        bodyPart.headers = mutableHeaders;
        bodyPart.boundary = self.boundary;
        bodyPart.body = fileURL;
        bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];
        [self.bodyStream appendHTTPBodyPart:bodyPart];
    
        return YES;
    }
    

    1、通过[fileURL isFileURL]来判断是不是文件的路径;
    2、通过[fileURL checkResourceIsReachableAndReturnError:error]判断该文件是否存在且可读;
    3、设置头部;
    4、创建AFHTTPBodyPart;
    5、把bodyPart拼接到bodyStream中去。

    bodyStream就是AFMultipartBodyStream,它是AFStreamingMultipartFormData的一个属性。AFMultipartBodyStream继承自NSInputStream:@interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate>,它的appendHTTPBodyPart方法其实就是把bodyPart加到一个数组中:

    - (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
        [self.HTTPBodyParts addObject:bodyPart];
    }
    

    bodyStream最后会被设置到request当中:[self.request setHTTPBodyStream:self.bodyStream];;

    AFMultipartFormData还有一个特别的方法:throttleBandwidthWithPacketSize:。throttle的意思是节流。可以通过throttleBandwidthWithPacketSize来设置bodyStream每次写到Buffer的数据量和延迟时间来提高请求的成功率。

    根据注释可以知道,但我们在比较弱的网络下上传文件时,有可能会遇到"request body stream exhausted"的错误,这时候我们可以在失败的回调里面通过设置throttled bandwidth进行重试。AF也提供了两个建议的值:kAFUploadStream3GSuggestedPacketSizekAFUploadStream3GSuggestedDelay

    我们通过TCP发送数据的时候,数据会被分成一个个小的数据包发送,这些Packet存在一个SendBuffer中的。在弱网络环境下,一个Packet的传输失败率会提高,但因为TCP提供可靠传输,一次失败TCP不会马上任务请求失败,而是会重试一段时间,同时TCP还要保证有序传输,这就导致后面的Packet继续等待。如果一次发送的数据比较大,那后面的Packet传输失败的可能性也会变高,也就意味着我们HTTP请求失败的几率会变大。

    可以看看AFMultipartBodyStream实现的方法:

    - (NSInteger)read:(uint8_t *)buffer
            maxLength:(NSUInteger)length
    {
        if ([self streamStatus] == NSStreamStatusClosed) {
            return 0;
        }
    
        NSInteger totalNumberOfBytesRead = 0;
    
        while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
            if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
                if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                    break;
                }
            } else {
                NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
                NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
                if (numberOfBytesRead == -1) {
                    self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                    break;
                } else {
                    totalNumberOfBytesRead += numberOfBytesRead;
    
                    if (self.delay > 0.0f) {
                        [NSThread sleepForTimeInterval:self.delay];
                    }
                }
            }
        }
    
        return totalNumberOfBytesRead;
    }
    

    它是在read方法里面限制TCP的Packet大小的,而且通过[NSThread sleepForTimeInterval:self.delay];来暂停读操作从而延迟请求。

    事实上,iOS中的Throttle还有好几种。比如我们快速点击某个按钮时,如果每次点击都执行某些操作,那就有可能导致页面卡顿。我们可以把某次点击之后一段时间间隔以内的点击都忽略掉,这也是一种Throttle。除此之外,iOS中Global Queue的DISPATCH_QUEUE_PRIORITY_BACKGROUND优先级也涉及到Disk I/O Throttle。

    iOS编程中throttle那些事
    一次磁盘读写操作涉及到的硬件资源主要有两个,CPU和磁盘。任务本身由CPU触发和调度,读操作发生时,CPU告知Disk去获取某个地址的数据,此时由于Disk的读操作存在寻址延迟,CPU是处于I/O wait状态,一直维持到Disk返回数据为止。处于I/O wait状态的CPU,此时并不能把这部分等待的时间用来处理其他任务,也就是说这一段等待的CPU时间被“浪费”了。而CPU是公共的系统资源,这部分资源的损耗自然会对系统的整体表现产生负面影响。即使Global Queue使用的是子线程,也会造成CPU资源的消耗。
    如果把任务的Priority调整为DISPATCH_QUEUE_PRIORITY_BACKGROUND,那么这些任务中的I/O操作就被被控制,部分I/O操作的启动时间很有可能被适当延迟,把更多的CPU资源腾出来处理其他任务(比如说一些系统资源的调度任务),这样可以让我们的系统更加稳定高效。简而言之,对于重度磁盘I/O依赖的后台任务,如果对实时性要求不高,放到DISPATCH_QUEUE_PRIORITY_BACKGROUND Queue中是个好习惯,对系统更友好。

    AFURLResponseSerialization数据解析

    AF实现了以下七种数据解析的类:

    • AFHTTPResponseSerializer
    • AFJSONResponseSerializer
    • AFXMLParserResponseSerializer
    • AFXMLDocumentResponseSerializer
    • AFPropertyListResponseSerializer
    • AFImageResponseSerializer

    这些类都继承了同一个协议:AFURLResponseSerialization

    - (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                               data:(nullable NSData *)data
                              error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
    

    当任务完成后会来到- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error方法,然后调用解析类来解析数据,拿到结果:

    responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
    

    responseSerializer在AFURLSessionManage初始化的时候创建,默认为AFJSONResponseSerializier:

    self.responseSerializer = [AFJSONResponseSerializer serializer];
    

    AFJSONResponseSerializer继承自AFHTTPResponseSerializer。

    AFJSONResponseSerializer解析数据时会先调用AFHTTPResponseSerializer的- (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error方法来判断返回的数据是否可用,主要有两个条件:
    1、返回的结果类型是可接受的类型;
    2、状态码是可接受的状态码。

    可接受的数据类型保存在self.acceptableContentTypes,AFJSONResponseSerializer初始化时设置了值:

     self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
    

    返回结果可用之后,就进入数据解析的环节:

    id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
    

    另外一段移除值为空的字段的代码:

    id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
        if ([JSONObject isKindOfClass:[NSArray class]]) {
            NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
            for (id value in (NSArray *)JSONObject) {
                if (![value isEqual:[NSNull null]]) {
                    [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
                }
            }
    
            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];
                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;
    }
    

    判断值是父为空:!value || [value isEqual:[NSNull null]]

    _AFURLSessionTaskSwizzling

    Method Swizzling

    Method Swizzling指的是改变一个已存在的选择器对应的实现的过程,就是可以在运行的过程中改变某个方法的实现。它和类别的区别在于,类别只能增加新的方法或覆盖有的方法,而无法调用原来的。

    一个完整的Method Swizzling示例如下:

    @implementation UIViewController (Logging)
    
    + (void)load {
        swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
    }
    
    - (void)swizzled_viewDidAppear:(BOOL)animated {
        // call original implementation
        [self swizzled_viewDidAppear:animated];
    
        // Logging
        [Logging logWithEventName:NSStringFromClass([self class])];
    }
    
    void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)   {
        // the method might not exist in the class, but in its superclass
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
        // class_addMethod will fail if original method already exists
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
        // the method doesn’t exist and we just added one
        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } 
        else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
    

    替换的主要流程为:

    1、通过class_getInstanceMethod方法拿到原有的方法(后面用Old方法表示)和即将要替换成的方法(后面用New方法表示)
    2、利用class_addMethod来判断该类有没有实现Old方法,因为class_getInstanceMethod拿到的方法可能是父类的,而我们并不想替换父类的方法。如果class_addMethod添加失败,则说明该类已经有此方法了,否则原本就没有实现;
    3、如果第2步的添加是成功的,则说明已经添加了Old方法,而这个Old方法的实现是New方法的,此时只需要将New方法的实现替换为Old方法的实现即可;如果步骤2是添加失败的,则说明Old方法原本就实现了,这样就需要调用method_exchangeImplementations来进行方法替换。

    替换的步骤会放在+load:中执行,是因为一般来说类别的方法会覆盖掉主类中原有的方法,而+load:是个特例。当一个类被读到内存时,runtime会给这个类和他的每个类别都发送一个+load:消息,这样就保证了 +load: 方法一定会执行到。

    替换完成之后,外部调用viewDidAppear:方法时,实际上执行的是swizzled_viewDidAppear:方法的实现,而在swizzled_viewDidAppear:的实现里面又调用了swizzled_viewDidAppear:方法,看似是递归,但此时swizzled_viewDidAppear:对应的实现已经被替换成viewDidAppear:方法的实现了,因此就做到了方法的替换。

    最终实际逻辑如以下代码:

    - (void)viewDidAppear:(BOOL)animated {
        // call original implementation
        [self swizzled_viewDidAppear:animated];
    
        // Logging
        [Logging logWithEventName:NSStringFromClass([self class])];
    }
    
    - (void)swizzled_viewDidAppear:(BOOL)animated {
        // 原有的实现
    }
    

    AF中Method Swizzling的应用

    以下为_AFURLSessionTaskSwizzling的源码:

    
    /**
     *  A workaround for issues related to key-value observing the `state` of an `NSURLSessionTask`.
     *
     *  See:
     *  - https://github.com/AFNetworking/AFNetworking/issues/1477
     *  - https://github.com/AFNetworking/AFNetworking/issues/2638
     *  - https://github.com/AFNetworking/AFNetworking/pull/2702
     */
    
    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 {
        /**
         WARNING: Trouble Ahead
         https://github.com/AFNetworking/AFNetworking/pull/2702
         */
    
        if (NSClassFromString(@"NSURLSessionTask")) {
            /**
             iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
             Many Unit Tests have been built to validate as much of this behavior has possible.
             Here is what we know:
                - NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
                - Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
                - On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
                - On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
                - On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
                - On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
                - Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
            
             Some Assumptions:
                - No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
                - No background task classes override `resume` or `suspend`
             
             The current solution:
                1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
                2) Grab a pointer to the original implementation of `af_resume`
                3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
                4) Grab the super class of the current class.
                5) Grab a pointer for the current class to the current implementation of `resume`.
                6) Grab a pointer for the super class to the current implementation of `resume`.
                7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
                8) Set the current class to the super class, and repeat steps 3-8
             */
            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;
    }
    
    - (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];
        }
    }
    
    - (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
    

    从注释中可以知道这个类主要是为了解决NSURLSessionTaskstate的监听的问题。因为直接对属性进行KOV监听会出现闪退的情况。而AF的解决思路是替换NSURLSessionTaskresumesuspend方法,这样在这两个方法调用时就知道任务的开始和暂停,然后再发出相应的通知。

    为了替换方法,首先要拿到方法,而拿到方法又要先去的当前的类。前面的示例中我们是通过[self class]来拿到类的,但在这里情况却有一点复杂。

    注释中说,iOS 7 和 iOS 8中NSURLSessionTask的实现是不一样的:

    • 1、NSURLSessionTasks are implemented with class clusters,meaning the class you request from the API isn't actually the type of class you will get back。通过API拿到的类并不是真正想要的。
    • 2、调用[NSURLSessionTask class]并没有用,你必须要利用NSURLSession创建一个NSURLSessionTask示例并从该实例拿到class。
    • 3、在iOS 7 中,localDataTask是一个__NSCFLocalDataTask,继承关系为__NSCFLocalDataTask -> __NSCFLocalSessionTask -> __NSCFURLSessionTask
    • 4、在iOS 8 中,localDataTask是一个 __NSCFLocalDataTask,继承关系为__NSCFLocalDataTask -> __NSCFLocalSessionTask -> NSURLSessionTask
    • 5、在iOS 7 中,只有__NSCFLocalSessionTask__NSCFURLSessionTask这两个类实现了resumesuspend方法,而且__NSCFLocalSessionTask 没有调用父类的方法,所以这两个类的方法都要被分别替换。
    • 6、在iOS 8 中,只要NSURLSessionTask实现了这两个方法,所以也只有它需要被替换。

    实现的步骤:

    • 1、利用NSURLSession来创建一个任务从而拿到__NSCFLocalDataTask的一个实例;
    • 2、拿到指向af_resume实现的指针originalAFResumeIMP;
    • 3、判断当前的NSURLSessionDataTask类是否实现了resume方法;
    • 4、拿到当前类的父类;
    • 5、拿到当前类的resume方法的实现的指针classResumeIMP;
    • 6、拿到父类的resume方法的实现的指针superclassResumeIMP;
    • 7、如果classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP,那么就开始交换方法;
    • 8、令当前类等于父类,再重复3-8。

    AF交换方法时没有利用class_addMethod来判断拿到的方法是不是父类的方法,是因为它在进入交换之前就已经做了判断:classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP,这样保证了拿到的方法不是父类的方法,所以它直接添加了ad_resume方法,然后进行交换。

    参考:

    相关文章

      网友评论

          本文标题:AFNetworking源码阅读(二)

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