帶你瞭解分類的加載流程

一.探索前需知swift


 1.1 程序在啓動時,編譯好的mach-o文件內容如何加載到App內存裏?
bash

 其實上篇文章已經介紹了程序一運行如何將編譯好的mach-o文件內容如何加載到App內存     裏,一開始回來到 _objc_init(void),程序初始化函數,在這裏會進行整個程序的一些環境配置、異常配置、靜態構析函數配置等,而後會map_images讀取鏡像文件的過程.函數

1.2 map_images 作了哪些工做?ui

void _read_images {
    
    // 1:第一次進來 - 開始建立表
    // gdb_objc_realized_classes : 全部類的表 - 包括實現的和沒有實現的
    // allocatedClasses: 包含用objc_allocateClassPair分配的全部類(和元類)的表。(已分配)
    if (!doneOnce) {
           doneOnce = YES;
        // namedClasses
        // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor
        int namedClassesSize =
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
    }
    
    // 2:類處理
    for (i = 0; i < count; i++) {
      Class cls = (Class)classlist[i];
      Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
    }
    
    // 3: 方法編號處理
    for (EACH_HEADER) {
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
          const char *name = sel_cname(sels[i]);
          sels[i] = sel_registerNameNoLock(name, isBundle);
        }
    }

    // 4: 協議處理
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        NXMapTable *protocol_map = protocols();
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map,
                         isPreoptimized, isBundle);
        }
    }
    
    // 5: 非懶加載類處理
    for (EACH_HEADER) {
      classref_t *classlist =
          _getObjc2NonlazyClassList(hi, &count);
      addClassTableEntry(cls);
      realizeClassWithoutSwift(cls);
    }
    
    // 6: 待處理的類
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls);
            cls->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }
    
    // 7:分類處理
   for (EACH_HEADER) {
       category_t **catlist =
           _getObjc2CategoryList(hi, &count);
       bool hasClassProperties = hi->info()->hasCategoryClassProperties();
       for (i = 0; i < count; i++) {
           category_t *cat = catlist[i];
           Class cls = remapClass(cat->cls);
       }
   }

}

複製代碼

  • 加載全部類到類的gdb_objc_realized_classes表中。
  • 對全部類作重映射。
  • 將全部SEL都註冊到namedSelectors表中。
  • 修復函數指針遺留。
  • 將全部Protocol都添加到protocol_map表中。
  • 對全部Protocol作重映射。
  • 初始化全部非懶加載的類,進行rwro等操做。
  • 遍歷已標記的懶加載的類,並作初始化操做。
  • 處理全部Category,包括ClassMeta Class
  • 初始化全部未初始化的類。

  • 1.3 什麼是非懶加載類和懶加載類?this

    咱們看到map_images裏有一個步驟是初始化全部非懶加載類,進行rwro等操做.那麼到底什麼狀況會初始化非懶加載類?咱們如今一個工程裏建立LGPerson、LGStudent、LGTeacher 三個類.atom


    而後在待處理類裏面打印出非加載的類:printf("non-lazy Class:%s\n",cls->mangledName());spa


    打印結果以下:ssr


    發現打印了LGStudent、LGTeacher 和系統的一大堆類,可是LGPerson爲何沒打印呢?3d

    原來LGStudent、LGTeacher裏實現了+(void)load的方法,而LGPerson卻沒有實現+(void)load的方法.因此能夠得出結論:非懶加載的類,就是實現了+(void)load的方法和靜態實例變量,其實也好理解+(void)load 方法調用在main函數調用以前,因此係統確定先幫你這個類先初始化了,因此就是非懶加載的,而懶加載的類就是沒有實現+(void)load,系統不幫你加載,等到你何時用何時加載這就是懶加載的類.指針

    1.4 懶加載的類什麼時候讀取並進行rwro等操做?

    懶加載的類是何時讀取的呢,是你第一次用到的時候,也就是你第一次建立並使用的時候.也就是


    看過我以前文章的盆友必定會知道,它在底層會發送消息,會先進行快速查找慢速查找流程,第一次進來的話會走慢速查找流程,來到 lookUpImpOrForward


    if (!cls->isRealized()) {
            cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
      }
    複製代碼

    由於這是懶加載的類,以前並無被實現因此必然回來到這個判斷裏來.

    realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
    {
        lock.assertLocked();
    
        if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
            // Non-Swift class. Realize it now with the lock still held.
            // fixme wrong in the future for objc subclasses of swift classes
            realizeClassWithoutSwift(cls);
            if (!leaveLocked) lock.unlock();
        } else {
            // Swift class. We need to drop locks and call the Swift
            // runtime to initialize it.
            lock.unlock();
            cls = realizeSwiftClass(cls);
            assert(cls->isRealized());    // callback must have provoked realization
            if (leaveLocked) lock.lock();
        }
    
        return cls;
    }
    複製代碼

    看裏面又來到了 realizeClassWithoutSwift,和非懶加載類後面的流程是如出一轍的 .

     二.分類加載的初探

    2.1  分類的結構

    若是一個開發者沒有底層objc的源碼,怎麼分析分類的結構,其實以前的文章也介紹這個方法 clang.

    首先咱們在工程裏建立個分類:


    而後打開終端 輸入命令:clang -rewrite-objc LGTeacher+test.m -o LGteacherTest.cpp(輸出個.cpp編譯後的文件)


    發現這個文件裏有 :

    static struct _category_t _OBJC_$_CATEGORY_LGTeacher_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) 

    test 表明類別的名字,_category_t 這個就是類別的結構體. 咱們在objc源碼裏搜索c:

    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    複製代碼

    原來 category_t 底層是一個結構體 ,裏面有name、cls、instanceMethods、classMethods、protocols、instanceProperties、_classProperties, name 就是類別的名字,

    cls就是類,instanceMethods 就是實例方法,classMethods就是類方法,protocols就是類別裏的協議.

    2.2 分類的加載

    既然分類的結構已經清楚了,下面咱們就要探索分類是如何加載到主類裏面的.

    LGTeacher.h

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LGTeacher : NSObject
    @property (nonatomic, copy) NSString *name;
    
    + (void)sayMaster;
    
    @end
    
    NS_ASSUME_NONNULL_END
    複製代碼

    LGTeacher.m

    #import "LGTeacher.h"
    
    @implementation LGTeacher
    
    + (void)load{
        NSLog(@"load");
    }
    
    + (void)sayMaster{
        NSLog(@"%s",__func__);
    }
    
    @end
    複製代碼

    LGTeacher+test.h

    #import <AppKit/AppKit.h>
    
    
    #import "LGTeacher.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LGTeacher (test)
    @property (nonatomic, copy) NSString *cate_p1;
    //@property (nonatomic, copy) NSString *cate_p2;
    
    //- (void)cate_instanceMethod1;
    - (void)cate_instanceMethod2;
    
    //+ (void)cate_classMethod1;
    + (void)cate_classMethod2;
    
    @end
    
    複製代碼

     LGTeacher+test.m

    #import "LGTeacher+test.h"
    #import <objc/runtime.h>
    
    #import <AppKit/AppKit.h>
    
    @implementation LGTeacher (test)
    
    + (void)load{
        NSLog(@"分類 load");
    }
    
    - (void)setCate_p1:(NSString *)cate_p1{
    }
    
    - (NSString *)cate_p1{
        return @"cate_p1";
    }
    
    - (void)cate_instanceMethod2{
        NSLog(@"%s",__func__);
    }
    
    + (void)cate_classMethod2{
        NSLog(@"%s",__func__);
    }
    @end
    複製代碼

     有些盆友會說,探究前已經分析過 read_images 裏有個方法,  

    // 7:分類處理

    for (EACH_HEADER) {
           category_t **catlist =
               _getObjc2CategoryList(hi, &count);
           bool hasClassProperties = hi->info()->hasCategoryClassProperties();
           for (i = 0; i < count; i++) {
               category_t *cat = catlist[i];
               Class cls = remapClass(cat->cls);
           }
       }
    }複製代碼

     在這裏會發現分類,而後走到:    

    // Process this category. 
                // First, register the category with its target class. 
                // Then, rebuild the class's method lists (etc) if // the class is realized. bool classExists = NO; if (cat->instanceMethods || cat->protocols || cat->instanceProperties) { addUnattachedCategoryForClass(cat, cls, hi); if (cls->isRealized()) { remethodizeClass(cls); classExists = YES; } if (PrintConnecting) { _objc_inform("CLASS: found category -%s(%s) %s", cls->nameForLogging(), cat->name, classExists ? "on existing class" : ""); } } 複製代碼

    由於category 裏是實例方法,因此必然會走 if (cat->instanceMethods || cat->protocols

    || cat->instanceProperties) 這裏面,addUnattachedCategoryForClass 添加沒有加進去的分類,

    在這裏進行添加的唄.那咱們就來驗證下, 首先咱們在addUnattachedCategoryForClass) 這句代碼中打上斷點,


    來分析此時的class結構:



    此時類裏面rwmethod裏有三個方法: cxx_destructsetNamegetName,

    而主類裏是:

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LGTeacher : NSObject
    @property (nonatomic, copy) NSString *name;
    
    + (void)sayMaster;
    
    @end
    
    NS_ASSUME_NONNULL_END
    複製代碼

    說明此時主類的方法以及被加載到rw裏了,由於第五步是非懶加載類已經初始化過了,主類的rw、ro都有東西啦,有的盆友可能會問: + (void)sayMaster 沒有加到呀,

    其實仍是之前文章提到過的問題, 類方法添加到元類裏,並非自己的類裏.

    咱們接着往下分析進入到addUnattachedCategoryForClass 裏面:


    這一步主要是將沒有加載的categoryList 關聯起來 而且加到NXMap 表裏.

    緊接着 又來到(第五步非懶加載已經初始化過,判斷裏必走):

    if (cls->isRealized()) {
        remethodizeClass(cls);
        classExists = YES;
    }
    複製代碼

    remethodizeClass  --> attachCategories -- >進入方法實現


    在這代碼最後打下斷點再分析 cls 結構:


    結構以下:





    分類裏的方法是否是也加載到rw 裏了,

    全部的一切一切都獲得了驗證,都是那麼看得見摸得着, 那麼分類的加載就分析完畢了嗎NO,NO,NO 這纔是冰山一角, 當前類、分類都實現了load 方法, 說明這是非懶加載類 非懶加載分類的搭配使用,還有更多狀況請看下面.

     三.分類加載的進階

    在上面咱們介紹了 懶加載類和非懶加載類,那麼一樣分類也分懶加載和非懶加載,因此它們一塊兒搭配使用就有四種狀況:

     懶加載的分類(不實現 load 方法 )

     3.11  懶加載的分類(不實現 load 方法 )與非懶加載的類搭配使用(實現 load 方法 )

    咱們知道非懶加載的類加載時,必然會走read_images - realizeClassWithoutSwift - methodlizeClass 


    咱們這打個斷點看看此時的類結構:



    個人天,此時ro裏面有分類的方法、屬性了,這該怎麼解釋呢? 其實這是由於在編譯的時候已經把分類的信息給讀取到ro裏面了.

    3.12 懶加載的分類(不實現 load 方法 )與懶加載的類搭配使用 (不實現 load 方法 )

    本身實現懶加載 :Class cls = [LGTeacher class];

    懶加載的類第一次調用時,它在底層會發送消息,會先進行快速查找慢速查找流程,第一次進來的話會走慢速查找流程,來到 lookUpImpOrForward


    分析此時的LGPerson結構, 注意哦 咱們上面調用的是對象方法,在方法查找中會在元類裏進行查找,因此這個方法中cls 參數表明 元類, 咱們來看下元類的結構:


    個人天, 元類的ro cate_classMethod2,也就是分類的方法.我把分類的代碼粘一下

    #import "LGTeacher+test.h"
    #import <objc/runtime.h>
    
    #import <AppKit/AppKit.h>
    
    @implementation LGTeacher (test)
    
    //+ (void)load{
    //    NSLog(@"分類 load");
    //}
    
    - (void)setCate_p1:(NSString *)cate_p1{
    }
    
    - (NSString *)cate_p1{
        return @"cate_p1";
    }
    
    - (void)cate_instanceMethod2{
        NSLog(@"%s",__func__);
    }
    
    + (void)cate_classMethod2{
        NSLog(@"%s",__func__);
    }
    @end
    
    複製代碼

    lookUpImpOrForward 剛進來時 ,元類裏的ro就已經有分類的方法了.由此能夠得出結論:

    懶加載的分類 方法- 編譯處理 - 直接處理 data() - ro


     非懶加載的分類(實現 load 方法 )

     3.21  懶加載的分類(實現 load 方法 )與非懶加載的類搭配使用(實現 load 方法 )

    上面初探時已經分析過,  read_images - realizeClassWithoutSwift - methodlizeClass - addUnattachedCategoryForClass - 判斷是否實現 - 這裏看到上面一行就在read_images 實現了:

    if (cls->isRealized()) {

     remethodizeClass(cls); -> 實現類信息

    } attachCategories 加載分類數據進來

    3.22  懶加載的分類(實現 load 方法 )與懶加載的類搭配使用(不實現 load 方法 )

    這種狀況就比較特殊, 按以前分析的  懶加載的分類 會走到 read_images -->

    // 分類的處理 

    for (EACH_HEADER) {
           category_t **catlist =
               _getObjc2CategoryList(hi, &count);
           bool hasClassProperties = hi->info()->hasCategoryClassProperties();
           for (i = 0; i < count; i++) {
               category_t *cat = catlist[i];
               Class cls = remapClass(cat->cls);
           }
       }
    }複製代碼

    懶加載的類以前分析會第一次調用時,它在底層會發送消息,會先進行快速查找慢速查找流程,第一次進來的話會走慢速查找流程,來到 lookUpImpOrForward.

    但在這裏想一下:運行時分類都加載好了,主類第一次加載還要等到第一次消息發送時,是否是太慢了?

    因此這裏和其它流程不同的地方是:先走個 prepare_load_methods :

    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertLocked();
    
        category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = categorylist[i];
            Class cls = remapClass(cat->cls);
            
            const class_ro_t *ro = (const class_ro_t *)cls->data();
            const char *cname = ro->name;
            const char *oname = "LGTeacher";//LGTeacher
            // printf("類名 :%s \n",cname);
            if (cname && (strcmp(cname, oname) == 0)) {
    //            printf("prepare_load_methods :非懶加載分類名 :%s \n",cname);
            }
            
            if (!cls) continue;  // category for ignored weak-linked class
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class extensions and categories on Swift "
                            "classes are not allowed to have +load methods");
            }
            realizeClassWithoutSwift(cls);
        }
    }
    複製代碼

    在這個方法裏面會有: realizeClassWithoutSwift(cls). 在這裏進行初始化類信息, rw的賦值.

    四.總結

    本篇文章先介紹了懶加載類非懶加載類,而後分4種組合狀況深刻研究了類的加載分類加載的原理.其中: 

    1.分類的懶加載在編譯期就已經肯定了.

    2. 分類的非懶加載在read_images 在非懶加載分類中addUnattachedCategoryForClass 添加沒有加進去的分類,最後remethodizeClass --> attachCategories,纔會對rw進行操做. 

    3.類的非懶加載也是在read_images裏進行非懶加載的類初始化.

    4.最後類的懶加載分爲兩種狀況:若是分類也是懶加載的話,它在底層會發送消息,會先進行快速查找慢速查找流程,第一次進來的話會走慢速查找流程,來到 lookUpImpOrForward,進行初始化類,若是分類也是非懶加載的話,先走個 prepare_load_methods 在這個方法裏面會有: realizeClassWithoutSwift(cls). 在這裏進行初始化類信息, rw的賦值.

    說明:文章中不恰當的地方,還但願看到的盆友提出,本身會及時改正

    相關文章
    相關標籤/搜索