版本記錄
| 版本號 | 時間 |
|---|---|
| V1.0 | 2018.03.03 |
前言
我們做APP發(fā)起網(wǎng)絡(luò)請求,都離不開一個非常有用的框架AFNetworking,可以說這個框架的知名度已經(jīng)超過了蘋果的底層網(wǎng)絡(luò)請求部分,很多人可能不知道蘋果底層是如何發(fā)起網(wǎng)絡(luò)請求的,但是一定知道
AFNetworking,接下來幾篇我們就一起詳細(xì)的解析一下這個框架。感興趣的可以看上面寫的幾篇。
1. AFNetworking源碼探究(一) —— 基本介紹
2. AFNetworking源碼探究(二) —— GET請求實現(xiàn)之NSURLSessionDataTask實例化(一)
3. AFNetworking源碼探究(三) —— GET請求實現(xiàn)之任務(wù)進(jìn)度設(shè)置和通知監(jiān)聽(一)
4. AFNetworking源碼探究(四) —— GET請求實現(xiàn)之代理轉(zhuǎn)發(fā)思想(一)
5. AFNetworking源碼探究(五) —— AFURLSessionManager中NSURLSessionDelegate詳細(xì)解析(一)
6. AFNetworking源碼探究(六) —— AFURLSessionManager中NSURLSessionTaskDelegate詳細(xì)解析(一)
7. AFNetworking源碼探究(七) —— AFURLSessionManager中NSURLSessionDataDelegate詳細(xì)解析(一)
8. AFNetworking源碼探究(八) —— AFURLSessionManager中NSURLSessionDownloadDelegate詳細(xì)解析(一)
9. AFNetworking源碼探究(九) —— AFURLSessionManagerTaskDelegate中三個轉(zhuǎn)發(fā)代理方法詳細(xì)解析(一)
10. AFNetworking源碼探究(十) —— 數(shù)據(jù)解析之?dāng)?shù)據(jù)解析架構(gòu)的分析(一)
11. AFNetworking源碼探究(十一) —— 數(shù)據(jù)解析之子類中協(xié)議方法的實現(xiàn)(二)
12. AFNetworking源碼探究(十二) —— 數(shù)據(jù)解析之子類中協(xié)議方法的實現(xiàn)(三)
13. AFNetworking源碼探究(十三) —— AFSecurityPolicy與安全認(rèn)證 (一)
14. AFNetworking源碼探究(十四) —— AFSecurityPolicy與安全認(rèn)證 (二)
15. AFNetworking源碼探究(十五) —— 請求序列化之架構(gòu)分析(一)
16. AFNetworking源碼探究(十六) —— 請求序列化之協(xié)議方法的實現(xiàn)(二)
回顧
上一篇主要講述了AFPropertyListRequestSerializer AFJSONRequestSerializer和 AFHTTPRequestSerializer中請求序列化的協(xié)議方法的實現(xiàn)。這一篇主要就是轉(zhuǎn)載一篇關(guān)于_AFURLSessionTaskSwizzling實現(xiàn)方法交換的文章。
_AFURLSessionTaskSwizzling
實現(xiàn)方法交換主要就是在這個類中,在文件中AFURLSessionManager。下面看一下該類的API。
@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
這個類大概的作用就是替換掉NSURLSession中的resume和suspend方法。正常處理原有邏輯的同時,多發(fā)送一個通知,以下是我們需要替換的新方法:
//被替換掉的方法,只要有TASK開啟或者暫停,都會執(zhí)行
- (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];
}
}
這塊知識是關(guān)于OC的Runtime:method swizzling的,如果有不清楚的地方,可以看看這里method swizzling--by冰霜或者自行查閱。
+ (void)load {
if (NSClassFromString(@"NSURLSessionTask")) {
// 1) 首先構(gòu)建一個NSURLSession對象session,再通過session構(gòu)建出一個_NSCFLocalDataTask變量
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
// 2) 獲取到af_resume實現(xiàn)的指針
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
// 3) 檢查當(dāng)前class是否實現(xiàn)了resume。如果實現(xiàn)了,繼續(xù)第4步。
while (class_getInstanceMethod(currentClass, @selector(resume))) {
// 4) 獲取到當(dāng)前class的父類(superClass)
Class superClass = [currentClass superclass];
// 5) 獲取到當(dāng)前class對于resume實現(xiàn)的指針
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
// 6) 獲取到父類對于resume實現(xiàn)的指針
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
// 7) 如果當(dāng)前class對于resume的實現(xiàn)和父類不一樣(類似iOS7上的情況),并且當(dāng)前class的resume實現(xiàn)和af_resume不一樣,才進(jìn)行method swizzling。
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
//執(zhí)行交換的函數(shù)
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
// 8) 設(shè)置當(dāng)前操作的class為其父類class,重復(fù)步驟3~8
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
原方法中有大量的英文注釋,我把它翻譯過來如下:
iOS 7和iOS 8在
NSURLSessionTask實現(xiàn)上有些許不同,這使得下面的代碼實現(xiàn)略顯trick。關(guān)于這個問題,大家做了很多Unit Test,足以證明這個方法是可行的目前我們所知的:
-
NSURLSessionTasks是一組class的統(tǒng)稱,如果你僅僅使用提供的API來獲取NSURLSessionTask的class,并不一定返回的是你想要的那個(獲取NSURLSessionTask的class目的是為了獲取其resume方法) - 簡單地使用
[NSURLSessionTask class]并不起作用。你需要新建一個NSURLSession,并根據(jù)創(chuàng)建的session再構(gòu)建出一個NSURLSessionTask對象才行。 - iOS 7上,
localDataTask(下面代碼構(gòu)造出的NSURLSessionDataTask類型的變量,為了獲取對應(yīng)Class)的類型是__NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自__NSCFURLSessionTask。 - iOS 8上,
localDataTask的類型為__NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自NSURLSessionTask - iOS 7上,
__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個實現(xiàn)了resume和suspend方法的類,另外__NSCFLocalSessionTask中的resume和suspend并沒有調(diào)用其父類(即__NSCFURLSessionTask)方法,這也意味著兩個類的方法都需要進(jìn)行method swizzling。 - iOS 8上,
NSURLSessionTask是唯一實現(xiàn)了resume和suspend方法的類。這也意味著其是唯一需要進(jìn)行method swizzling的類 - 因為
NSURLSessionTask并不是在每個iOS版本中都存在,所以把這些放在此處(即load函數(shù)中),比如給一個dummy class添加swizzled方法都會變得很方便,管理起來也方便。
下面是一些假設(shè)前提
- 目前iOS中resume和suspend的方法實現(xiàn)中并沒有調(diào)用對應(yīng)的父類方法。如果日后iOS改變了這種做法,我們還需要重新處理。
- 沒有哪個后臺task會重寫resume和suspend函數(shù)
其余的一部分翻譯在注釋中,對應(yīng)那一行代碼。大概總結(jié)下這個注釋:
- 其實這是被社區(qū)大量討論的一個bug,之前AF因為這個替換方法,會導(dǎo)致偶發(fā)性的crash,如果不要這個swizzle則問題不會再出現(xiàn),但是這樣會導(dǎo)致AF中很多UIKit的擴(kuò)展都不能正常使用。
- 原來這是因為iOS7和iOS8的
NSURLSessionTask的繼承鏈不同導(dǎo)致的,而且在iOS7繼承鏈中會有兩個類都實現(xiàn)了resume和suspend方法。而且子類沒有調(diào)用父類的方法,我們則需要對著兩個類都進(jìn)行方法替換。而iOS8只需要對一個類進(jìn)行替換。 - 對著注釋看,上述方法代碼不難理解,用一個while循環(huán),一級一級去獲取父類,如果實現(xiàn)了resume方法,則進(jìn)行替換。
但是有幾個點大家可能會覺得疑惑的,我們先把這個方法調(diào)用的替換的函數(shù)一塊貼出來。
//其引用的交換的函數(shù):
+ (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));
}
}
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));
}
我們來分析分析大家可能會覺得疑惑的地方:
- 首先可以注意
class_getInstanceMethod這個方法,它會獲取到當(dāng)前類繼承鏈逐級往上,第一個實現(xiàn)的該方法。所以說它獲取到的方法不能確定是當(dāng)前類還是父類的。而且這里也沒有用dispatch_once_t來保證一個方法只交換一次,那萬一這是父類的方法,當(dāng)前類換一次,父類又換一次,不是等于沒交換么?...請注意這行判斷:
// 7) 如果當(dāng)前class對于resume的實現(xiàn)和父類不一樣(類似iOS7上的情況),并且當(dāng)前class的resume實現(xiàn)和af_resume不一樣,才進(jìn)行method swizzling。
if (classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) {
//執(zhí)行交換的函數(shù)
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
這個條件就杜絕了這種情況的發(fā)生,只有當(dāng)前類實現(xiàn)了這個方法,才可能進(jìn)入這個if塊。
- 那iOS7兩個類都交換了
af_resume,那豈不是父類換到子類方法了? 只能說又是沒仔細(xì)看代碼的...注意AF是去向當(dāng)前類添加af_resume方法,然后去交換當(dāng)前類的af_resume。所以說根本不會出現(xiàn)這種情況...
后記
本篇主要講述了
_AFURLSessionTaskSwizzling用于方法交換。
