AFNetworking源碼探究(十七) —— _AFURLSessionTaskSwizzling實現(xiàn)方法交換(轉(zhuǎn)載)(一)

版本記錄

版本號 時間
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 AFJSONRequestSerializerAFHTTPRequestSerializer中請求序列化的協(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用于方法交換。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容