FLEX源碼分析二(網(wǎng)絡(luò)監(jiān)測swizzle)

這次分析網(wǎng)絡(luò)監(jiān)測這塊,因為這功能平時用于接口調(diào)試非常多。

核心類

網(wǎng)絡(luò)監(jiān)測涉及到的類如上圖。

  • 最為主要的兩個類FLEXNetworkObserver、FLEXNetworkRecorder。
  • UI相關(guān)的類有FLEXNetworkSettingsTableViewController、FLEXNetworkTransactionDetailTableViewController、FLEXNetworkTransactionTableViewCell。
  • 數(shù)據(jù)模型相關(guān)的類FLEXNetworkTransaction

根據(jù)類名大致能猜到FLEXNetworkObserver用于網(wǎng)絡(luò)監(jiān)測,而FLEXNetworkRecorder用于網(wǎng)絡(luò)記錄。

FLEXNetworkObserver

首先為了監(jiān)測系統(tǒng)類的行為,iOS中常用的方式就是swizzles。業(yè)界有個比較牛逼的名稱,面向切片編程,說的就是這中方式。

介紹
FLEXNetworkObserver 通過swizzleNSURLConnection和NSURLSession兩個類的代理方法來達(dá)到監(jiān)測整個URL加載系統(tǒng)。
FLEXNetworkRecorder 用于維護(hù)請求歷史記錄和緩存響應(yīng)結(jié)果

注入NSURLConnection和NSURLSession代理

一般情況下都是在+ (void)load方法中進(jìn)行swizzle。對于加入運(yùn)行期系統(tǒng)中的每個類及分類來說,必定會調(diào)用此方法,而且僅僅調(diào)用一次。當(dāng)包含類或分類的程序庫載入系統(tǒng)時,就會執(zhí)行此方法,而這通常就是指應(yīng)用程序啟動的時候。

通過觀察堆棧我們可以看到更為詳細(xì)的調(diào)用信息:


大致調(diào)用順序如下:

  • dyld 開始將程序二進(jìn)制文件初始化
  • 交由 ImageLoader 讀取 image,其中包含了我們的類、方法等各種符號
  • 由于 runtime 向 dyld 綁定了回調(diào),當(dāng) image 加載到內(nèi)存后,dyld 會通知 runtime 進(jìn)行處理
  • runtime 接手后調(diào)用 map_images 做解析和處理,接下來 load_images 中調(diào)用 call_load_methods 方法,遍歷所有加載進(jìn)來的 Class,按繼承層級依次調(diào)用 Class 的 +load 方法和其 Category 的 +load 方法

如果想了解整個類加載詳細(xì)過程可以看看這里iOS 程序 main 函數(shù)之前發(fā)生了什么

在注入的時候通常只注入一次。這里就通過單例的寫法如下:

static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // your code ...
}

swizzle所有代理

swizzle的入口是injectIntoAllNSURLConnectionDelegateClasses。swizzle所有實現(xiàn)了NSURLConnection和NSURLSession代理類,而且代理方法多,這里用了一個數(shù)組保持swizzle的方法。

const SEL selectors[] = {
            @selector(connectionDidFinishLoading:),
            @selector(connection:willSendRequest:redirectResponse:),
            @selector(connection:didReceiveResponse:),
            @selector(connection:didReceiveData:),
            @selector(connection:didFailWithError:),
            @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:),
            @selector(URLSession:dataTask:didReceiveData:),
            @selector(URLSession:dataTask:didReceiveResponse:completionHandler:),
            @selector(URLSession:task:didCompleteWithError:),
            @selector(URLSession:dataTask:didBecomeDownloadTask:delegate:),
            @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:),
            @selector(URLSession:downloadTask:didFinishDownloadingToURL:)
        };

怎樣才能獲得所有的類呢?runtime有一個方法可以直接獲取到objc_getClassList。文檔注釋如下:

/**

  • Obtains the list of registered class definitions.
  • @param buffer An array of \c Class values. On output, each \c Class value points to
  • one class definition, up to either \e bufferCount or the total number of registered classes,
  • whichever is less. You can pass \c NULL to obtain the total number of registered class
  • definitions without actually retrieving any class definitions.
  • @param bufferCount An integer value. Pass the number of pointers for which you have allocated space
  • in \e buffer. On return, this function fills in only this number of elements. If this number is less
  • than the number of registered classes, this function returns an arbitrary subset of the registered classes.
  • @return An integer value indicating the total number of registered classes.
  • @note The Objective-C runtime library automatically registers all the classes defined in your source code.
  • You can create class definitions at runtime and register them with the \c objc_addClass function.
  • @warning You cannot assume that class objects you get from this function are classes that inherit from \c NSObject,
  • so you cannot safely call any methods on such classes without detecting that the method is implemented first.
    */

根據(jù)上面文檔的意思,獲取加載類的總共數(shù)量:int numClasses = objc_getClassList(NULL, 0);
接下來的邏輯就比較簡單了

  1. 遍歷所有加載的類
  2. 遍歷每個類的方法列表
  3. 遍歷需要swizzle的方法數(shù)組,匹配方法是否需要swizzle

一共三層循環(huán),簡化代碼如下。

for (NSInteger classIndex = 0; classIndex < numClasses; ++classIndex) {
                Class class = classes[classIndex];

                if (class == [FLEXNetworkObserver class]) {
                    continue;
                }

                // 使用runtime而不用NSObject的方法是為了避免消息發(fā)送,這樣效率更高
                // 有一些類沒有在這里swizzle,F(xiàn)LEX同樣在+initialize 方法中swizzle了所有類
                // 注意了: 調(diào)用 class_getInstanceMethod() 會 像類發(fā)送 +initialize消息. 這也是為什么FLEX遍歷方法列表的原因。
                unsigned int methodCount = 0;
                Method *methods = class_copyMethodList(class, &methodCount);// 獲得方法總數(shù)
                BOOL matchingSelectorFound = NO;
                for (unsigned int methodIndex = 0; methodIndex < methodCount; methodIndex++) {
                    for (int selectorIndex = 0; selectorIndex < numSelectors; ++selectorIndex) {
                        if (method_getName(methods[methodIndex]) == selectors[selectorIndex]) {
                            // 如果實現(xiàn)了NSURLConnection和NSURLSession代理則swizzle
                            [self injectIntoDelegateClass:class];
                            matchingSelectorFound = YES;
                            break;
                        }
                    }
                    if (matchingSelectorFound) {
                        break;
                    }
                }
                free(methods);
            }
            
            free(classes);
        }

具體swizzle過程

由于swizzle代理方法過程是一樣的所以這里選取NSURLConnectionDelegate中的connection:willSendRequest:redirectResponse:說明。

基本思路其實就是如下兩張圖:


Objective-C的動態(tài)特性,可以實現(xiàn)在運(yùn)行時偷換selector對應(yīng)的方法實現(xiàn),達(dá)到給方法掛鉤的目的。
每個類都有一個方法列表,存放著selector的名字和方法實現(xiàn)的映射關(guān)系。IMP有點類似函數(shù)指針,指向具體的Method實現(xiàn)。

歸根結(jié)底,都是偷換了selector的IMP。

因為可以把Block轉(zhuǎn)換為IMP,通過imp_implementationWithBlock實現(xiàn)。這也是一個非常知名開源庫BLockKit的原理。

捋一捋思路:

  1. 得到原代理方法的selector,得到新定義的swizzledSelector。準(zhǔn)備將swizzledSelector指向selector的imp.
  2. 得到原代理方法的方法描述(如果不是swizzle代理方法,沒有這步)
  3. 定義兩個Block(Block參數(shù)與返回值要和代理方法一樣),用于swizzle原有selector。這里為什么要定義兩個呢。因為可能存在雖然有代理頭文件,但是并沒有真真的實現(xiàn)代理,而且因為已經(jīng)實現(xiàn)了代理,需要防止重復(fù)嗅探,因為父類如果實現(xiàn)了代理,只要調(diào)用原來的imp,父類的imp就會執(zhí)行。這樣一共就嗅探了兩次。
  4. 進(jìn)行swizzle,判斷是否實現(xiàn)過代理,如果實現(xiàn)了,就把實現(xiàn)的block轉(zhuǎn)換為imp。如果沒有實現(xiàn)就用默認(rèn)的block。

來看點代碼:

 // 參數(shù)和返回值和代理一樣的Block
    typedef NSURLRequest *(^NSURLConnectionWillSendRequestBlock)(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response);
    // 沒有實現(xiàn)代理block,在這里進(jìn)行網(wǎng)絡(luò)嗅探
    NSURLConnectionWillSendRequestBlock undefinedBlock = ^NSURLRequest *(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) {
        // 網(wǎng)絡(luò)嗅探,保存網(wǎng)絡(luò)請求狀態(tài)
        [[FLEXNetworkObserver sharedObserver] connection:connection willSendRequest:request redirectResponse:response delegate:slf];
        return request;
    };
    // 有實現(xiàn)代理的block
    NSURLConnectionWillSendRequestBlock implementationBlock = ^NSURLRequest *(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) {
        __block NSURLRequest *returnValue = nil;
        // 防止重復(fù)嗅探
        [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
            undefinedBlock(slf, connection, request, response);
        } originalImplementationBlock:^{
            // 原始方法
            returnValue = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, connection, request, response);
        }];
        return returnValue;
    };

感覺有必要把防止重復(fù)嗅探這個部分好好說一下,一萬自己在理解這部分的時候花了不少的時間。

sniffWithoutDuplicationForObject

之前出現(xiàn)了一個bug。參數(shù)object可能為空,這種情況下直接調(diào)用原有imp即可,之前沒有做這樣對空的情況的處理。

究竟是如何來保證值嗅探最初的網(wǎng)絡(luò)請求呢(相比于父類也有實現(xiàn))。通過如下代碼實現(xiàn)

    const void *key = selector;
    // 是否已經(jīng)標(biāo)記過,標(biāo)記過則不再嗅探
    if (!objc_getAssociatedObject(object, key)) {
        sniffingBlock();
    }

    // 標(biāo)記已經(jīng)在最初的時候嗅探過
    objc_setAssociatedObject(object, key, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    // 調(diào)用原來的(調(diào)用到了父類的執(zhí)行,因為父類同樣被swizzle這樣父類中的objc_getAssociatedObject(object, key)值就是為YES,不會再次被嗅探)
    originalImplementationBlock();
    objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

方法交換

到這里就相對簡單一點。將傳入的Block通過imp_implementationWithBlock轉(zhuǎn)為IMP。

接下來就是最為常見的swizzle代碼

    Method oldMethod = class_getInstanceMethod(cls, selector);
    if (oldMethod) {
    // 如果之前存在則先添加新方法,然后交換方法
        class_addMethod(cls, swizzledSelector, implementation, methodDescription.types);
        Method newMethod = class_getInstanceMethod(cls, swizzledSelector);
        method_exchangeImplementations(oldMethod, newMethod);
    } else {
    // 不存在則添加方法
        class_addMethod(cls, selector, implementation, methodDescription.types);
    }

THE END

寫了這么多才僅僅介紹了網(wǎng)絡(luò)部分注入過程中的Swizzle的使用。??!關(guān)于網(wǎng)絡(luò)部分還有很多的要寫??磥淼梅趾脦灼榻B了。今天就這樣吧!

擴(kuò)展閱讀

iOS 程序 main 函數(shù)之前發(fā)生了什么
Crasher in FLEXNetworkObserver

最后編輯于
?著作權(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)容