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 字符串。
【AFNetworking源码阅读(二)】至此一个NSMutableURLRequest就创建完成了。
除了requestWithMethod创造NSMutableURLRequest之外,AFHTTPRequestSerializer还有另外两个创建Request的方法:
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(idformData))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 (^)(idformData))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 = https://www.it610.com/article/(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那些事

    推荐阅读