iOS-Category詳解
阿新 • • 發佈:2021-01-19
Category的底層結構
分類底層結構定義的如下:
//objc-runtime-new.h 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物件。
Category原始碼閱讀順序:
- objc-os.mm (runtime入口)
- _objc_init (runtime初始化)
- map_images
- map_images_nolock
- objc-runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- attachLists
- realloc、memmove、 memcpy
category的載入過程:
- 通過runtime載入類的所有分類
- 將所有分類的方法,屬性,協議分別合併到一個數組
- 將合併後的分類資料插入到類原來到資料之前
由原始碼可見,對同名方法而言,會優先呼叫分類中的方法。如果多個分類中包含同名方法,則會呼叫最後參與編譯的分類中的方法。
摘錄原始碼中核心的attachCategories實現如下(objc4-756.2):
// Attach method lists and properties and protocols from categories to a class. // Assumes the categories in cats are all loaded and sorted by load order, // oldest categories first. static void attachCategories(Class cls, category_list *cats, bool flush_caches) { if (!cats) return; if (PrintReplacedMethods) printReplacements(cls, cats); bool isMeta = cls->isMetaClass(); // fixme rearrange to remove these intermediate allocations //方法二維陣列 //[[method_t,method_t],[method_t,method_t,method_t]] //二維陣列中的一個元素(陣列)存放一個分類中的方法列表 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)); // Count backwards through cats to get newest categories first int mcount = 0; int propcount = 0; int protocount = 0; int i = cats->count; bool fromBundle = NO; while (i--) { //取出分類 auto& entry = cats->list[i]; //取出分類中的方法列表 method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { mlists[mcount++] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { proplists[propcount++] = proplist; } protocol_list_t *protolist = entry.cat->protocols; if (protolist) { protolists[protocount++] = protolist; } } //取出(元)類物件中的資料(class_rw_t) auto rw = cls->data(); prepareMethodLists(cls, mlists, mcount, NO, fromBundle); //將所有分類的方法新增到(元)類物件的方法列表中 rw->methods.attachLists(mlists, mcount); free(mlists); if (flush_caches && mcount > 0) flushCaches(cls); //將所有分類的屬性新增到(元)類物件的屬性列表中 rw->properties.attachLists(proplists, propcount); free(proplists); //將所有分類的協議新增到(元)類物件的協議列表中 rw->protocols.attachLists(protolists, protocount); free(protolists); }
Category和Extension的區別
區別一
- Category
- Extension
- 可以說是特殊的分類,也稱作匿名分類
- 可以給類新增成員屬性,但是是私有變數
- 可以給類新增方法,也是私有方法
區別二
雖然有人說Extension是一個特殊的Category,也有人將Extension成為匿名分類,但是兩者的區別很大。
-
Category
- 是執行期決定的
- 類擴充套件可以新增例項變數,分類不能新增例項變數(原因:因為在執行期,物件的記憶體佈局已經確定,如果新增例項變數會破壞類的內部佈局,這對編譯性語言是災難性的。)
-
Extension
- 在編譯器決定,是類的一部分,在編譯器和標頭檔案的
@interface
和實現檔案裡的@implement
一起形成了一個完整的類。 - 伴隨著類的產生而產生,也隨著類的消失而消失。
- Extension一般用來隱藏類的私有訊息,必須有一個類的原始碼才能新增一個類的Extension,所以對於系統的一個類,比如NSString,就無法新增類擴充套件。
- 在編譯器決定,是類的一部分,在編譯器和標頭檔案的
1.Category 基本使用場合
經常用的是給類新增方法,協議、屬性,編寫的分類裡面的方法, 最終是在執行過程中的時候合併到類物件(物件方法)/元類物件(類方法)裡面 (不是編譯的時候合併的)