AFNetworking源码阅读(二)

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  * 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  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  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里面的方法:

- (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  *)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 ,它的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  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到底做了什么?(二)
  • Method Swizzling 和 AOP 实践
  • What does enctype='multipart/form-data' mean?
  • iOS编程中throttle那些事

你可能感兴趣的:(AFNetworking源码阅读(二))