1. 程式人生 > >iOS分類底層實現原理小記

iOS分類底層實現原理小記

tag side 遍歷 一個 instance ati strip 否則 取出

http://www.jianshu.com/p/b7169a5a558e

OS 分類底層是怎麽實現的?
本文將分如下四個模塊進行探究

  1. 分類的結構體
  2. 編譯時的分類
  3. 分類的加載
  4. 總結

本文使用的runtime源碼版本是 objc4 - 680
文中類與分類代碼如下

//類
@interface Person : NSObject
@property (nonatomic ,copy) NSString *presonName;
@end

@implementation Person
- (void)doSomeThing{
    NSLog(@"Person");
}
@end
// 分類
@interface Person(categoryPerson)
@property (nonatomic ,copy) NSString *categoryPersonName;
@end

@implementation Person(categoryPerson)
- (void)doSomeThing{
    NSLog(@"categoryPerson");
}
@end

1.分類的結構體

struct _category_t {
    const char *name;//類名
    struct _class_t *cls;//類
    const struct _method_list_t *instance_methods;//category中所有給類添加的實例方法的列表(instanceMethods)
    const struct _method_list_t *class_methods;//category中所有添加的類方法的列表(classMethods)
    const struct _protocol_list_t *protocols;//category實現的所有協議的列表(protocols)
    const struct _prop_list_t *properties;//category中添加的所有屬性(instanceProperties)
};

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; // 屬性列表

    // 如果是元類,就返回類方法列表;否則返回實例方法列表
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) {
            return classMethods;
        } else {
            return instanceMethods;
        }
    }

    // 如果是元類,就返回 nil,因為元類沒有屬性;否則返回實例屬性列表,但是...實例屬性
    property_list_t *propertiesForMeta(bool isMeta) {
        if (isMeta) {
            return nil; // classProperties;
        } else {
            return instanceProperties;
        }
    }
};

2.編譯時的分類

2.1分類的屬性

// Person(categoryPerson) 屬性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"categoryPersonName","T@\"NSString\",C,N"}}
};

// Person 屬性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"presonName","T@\"NSString\",C,N,V_presonName"}}
};

對比上述代碼可以發現:在分類中可以聲明屬性,並且同樣會生成一個 _prop_list_t 的結構體

2.2分類的實例變量?

// Person 實例變量列表

static struct /*_ivar_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[1];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    1,
    {{(unsigned long int *)&OBJC_IVAR_$_Person$_presonName, "_presonName", "@\"NSString\"", 3, 8}}
};

因為 _category_t 這個結構體中並沒有 _ivar_list_t
所以在編譯時系統沒有Person(categoryPerson) 沒有生成類似的相應結構體,也沒有生成 _categoryPersonName

2.3分類的實例方法

// Person 實例方法結構體
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[3];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    3,
    {{(struct objc_selector *)"doSomeThing", "[email protected]:8", (void *)_I_Person_doSomeThing},
    {(struct objc_selector *)"presonName", "@[email protected]:8", (void *)_I_Person_presonName},
    {(struct objc_selector *)"setPresonName:", "[email protected]:[email protected]", (void *)_I_Person_setPresonName_}}
};

// Person(categoryPerson )實例方法結構體
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"doSomeThing", "[email protected]:8", (void *)_I_Person_categoryPerson_doSomeThing}}
};

對比上述方法可以看到:雖然分類可以聲明屬性,但是編譯時,系統並沒有生成分類屬性的 get/set 方法,所以,這就是為什麽分類要利用
runtime 動態添加屬性,如何動態添加屬性,有興趣的同學可以查看下面文章 iOS分類中通過runtime添加動態屬性

2.4分類的結構體

// Person(categoryPerson ) 結構體
static struct _category_t _OBJC_$_CATEGORY_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_categoryPerson,
    0,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_categoryPerson,
};

這是系統在編譯時實例化 _category_t 生成的
_OBJC_$_CATEGORY_Person_$_categoryPerson

2.5分類數組

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_Person_$_categoryPerson,
};

編譯器最後生成了一個數組,數組的元素就是我們創建的各個分類,用來在運行時加載分類。

3.分類的加載

3.1加載分類調用棧

_objc_init
└──map_2_images
    └──map_images_nolock
        └──_read_images

分類加載的調用棧如上述

  • _objc_init 算是整個 objc4 的入口,進行了一些初始化操作,註冊了鏡像狀態改變時的回調函數
  • map_2_images 主要是加鎖並調用 map_images_nolock
  • map_images_nolock 在這個函數中,完成所有 class 的註冊、fixup等工作,還有初始化自動釋放池、初始化 side table 等工作並在函數後端調用了 _read_images
  • _read_images 方法幹了很多苦力活,比如加載類、Protocol、Category,加載分類的代碼就寫在 _read_images 函數的尾部

該調用棧入口函數 void _objc_init(void)objc-os.mm 中,有興趣的同學可以去看看這些函數裏都做了什麽

3.2 _read_images 中加載分類的源碼

加載分類的源碼主要做了兩件事

  • 把category的實例方法、協議以及屬性添加到類上
  • 把category的類方法和協議添加到類的metaclass上

略去與本文無關的代碼,得到如下代碼

// 獲取 鏡像中的所有分類
category_t **catlist = _getObjc2CategoryList(hi, &count);
// 遍歷 catlist
for (i = 0; i < count; i++) {
    category_t *cat = catlist[i];
    Class cls = remapClass(cat->cls);
    if (cat->instanceMethods ||  cat->protocols
        ||  cat->instanceProperties) 
    {
        addUnattachedCategoryForClass(cat, cls, hi);
        if (cls->isRealized()) {
            remethodizeClass(cls);
            classExists = YES; 
        }
    }
    if (cat->classMethods  ||  cat->protocols
        /* ||  cat->classProperties */)
    {
        addUnattachedCategoryForClass(cat, cls->ISA(), hi);
        if (cls->ISA()->isRealized()) {
            remethodizeClass(cls->ISA());
        }
    }
}

做上述事情主要用到是如下兩個函數

  • addUnattachedCategoryForClass(cat, cls, hi) 為類添加未依附的分類
    執行過程偽代碼:
    1.取得存儲所有 unattached 分類的列表

    NXMapTable *cats = unattachedCategories();

    2.從 cats 列表中找倒 cls 對應的 unattached 分類的列表

    category_list *list = (category_list *)NXMapGet(cats, cls);

    3.將新來的分類 cat 添加剛剛開辟的位置上

    list->list[list->count++] = (locstamped_category_t){cat, catHeader};

    4.將新的 list 重新插入 cats 中,會覆蓋老的 list

    NXMapInsert(cats, cls, list);

    執行完上述過程後,系統將這個分類放到了一個 cls 對應的 unattached 分類的 list 中(有點繞口....),這個 list 會在 remethodizeClass(cls) 方法用到

  • remethodizeClass(cls)
    執行過程偽代碼:
    1.取得 cls 類的 unattached 的分類列表

    category_list *cats = unattachedCategoriesForClass(cls, false/*not realizing*/)

    2.將 unattached 的分類列表 attach 到 cls 類上

    attachCategories(cls, cats, true /* 清空方法緩存 flush caches*/);

    執行完上述過程後,系統就把category的實例方法、協議以及屬性添加到類上

  • 最後再來看一下
    attachCategories(cls, cats, true /* 清空方法緩存 flush caches*/)內部的實現過程
    1.在堆上創建方法、屬性、協議數組,用來存儲分類的方法、屬性、協議

    method_list_t **mlists = (method_list_t **)malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)malloc(cats->count * sizeof(*protolists));

    2.遍歷 cats ,取出各個分類的方法、屬性、協議,並填充到上述代碼創建的數組中

    int mcount = 0; // 記錄方法的數量
    int propcount = 0; // 記錄屬性的數量
    int protocount = 0; // 記錄協議的數量
    int i = cats->count; // 從後開始,保證先取最新的分類
    bool fromBundle = NO; // 記錄是否是從 bundle 中取的
    while (i--) { // 從後往前遍歷
      auto& entry = cats->list[i]; // 分類,locstamped_category_t 類型
      // 取出分類中的方法列表;如果是元類,取得的是類方法列表;否則取得的是實例方法列表
      method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
      if (mlist) {
          mlists[mcount++] = mlist; // 將方法列表放入 mlists 方法列表數組中
          fromBundle |= entry.hi->isBundle(); // 分類的頭部信息中存儲了是否是 bundle,將其記住
      }
      // 取出分類中的屬性列表,如果是元類,取得是nil
      property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
      if (proplist) {
          proplists[propcount++] = proplist; // 將屬性列表放入 proplists 屬性列表數組中
      }
      // 取出分類中遵循的協議列表
      protocol_list_t *protolist = entry.cat->protocols;
      if (protolist) {
          protolists[protocount++] = protolist; // 將協議列表放入 protolists 協議列表數組中
      }
    }

    3.取出 cls 的 class_rw_t 數據

    auto rw = cls->data();

    4.存儲方法、屬性、協議數組到 rw

    // 準備 mlists 中的方法
    prepareMethodLists(cls, mlists, mcount/*方法列表的數量*/, NO/*不是基本方法*/, fromBundle/*是否來自bundle*/);
    // 將新屬性列表添加到 rw 中的屬性列表數組中
    rw->properties.attachLists(proplists, propcount);
    // 釋放 proplists
    free(proplists); // 釋放 proplists
    // 將新協議列表添加到 rw 中的協議列表數組中
    rw->protocols.attachLists(protolists, protocount);
    // 釋放 protolists
    free(protolists); // 釋放 protolists
    // 將新協議列表添加到 rw 中的協議列表數組中
    rw->protocols.attachLists(protolists, protocount);
    // 釋放 protolists
    free(protolists);

4.總結

至此,本文接近尾聲
希望本文的讀者能夠了解到

  • 分類的結構體
  • 分類中是否能添加屬性
  • 分類中是否有實例變量
  • 分類是如何 attach 到類上的

行筆簡陋,如有問題,敬請指正

iOS分類底層實現原理小記