Categroy底層原理
一、Category概念?
Category
是Objective-C 2.0之後新增的語言特性,分類、類別其實都是指的Category
。Category
的主要作用是為已經存在的類新增方法。
可以把類的實現分開在幾個不同的檔案裡面,這樣做有幾個好處,如下
- 1.減少單個檔案的體積
- 2.把不同的功能組織到不同的category裡
- 3.由多個開發者共同完成一個類
- 4.按需載入想要的category
- 5.宣告私有方法
二、Category原始碼分析
RMPerson
#import <Foundation/Foundation.h> @interface RMPerson : NSObject @end #import "RMPerson.h" @implementation RMPerson @end
RMPerson+(Test)
#import "RMPerson.h" @interface RMPerson (Test) <NSCopying> - (void)text; + (void)text1; @property (nonatomic, assign) int age; @property (nonatomic, assign) double weight; @end ---------------------------------------------------------- #import "RMPerson+Test.h" @implementation RMPerson (Test) - (void)text { NSLog(@"TEST---111111111111"); } + (void)text1 { NSLog(@"TEST---222222222222"); } @end
RMPerson類和RMPerson分類-RMPerson+(Test),我們通過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc RMPerson+(Test).m
將RMPerson+(Test)
轉換成C/C++原始碼。窺探下原始碼的內容(由於內容比較多,上重要的部分)
分類結構體
struct _category_t { const char *name;//類名稱 struct _class_t *cls;//類指標 const struct _method_list_t *instance_methods; //物件方法列表 const struct _method_list_t *class_methods; //類方法列表 const struct _protocol_list_t *protocols; //協議方法列表 const struct _prop_list_t *properties; //屬性列表 };
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { // Discover categories. 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); if (!cls) { // Category's target class is missing (probably weak-linked). // Disavow any knowledge of this category. catlist[i] = nil; if (PrintConnecting) { _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " "missing weak-linked target class", cat->name, cat); } continue; } // 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" : ""); } } if (cat->classMethods|| cat->protocols || (hasClassProperties && cat->_classProperties)) { addUnattachedCategoryForClass(cat, cls->ISA(), hi); if (cls->ISA()->isRealized()) { remethodizeClass(cls->ISA()); } if (PrintConnecting) { _objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name); } } } } }
由上面原始碼我們閱讀可得出
- 1.將分類的物件方法、物件協議方法、物件屬性整理到類物件中
-
2.將分類的類方法整理到元類物件中
而從原始碼中,我們可注意到,無論哪種整理都是通過呼叫static void remethodizeClass(Class cls)
函式來重新整理類的資料,下面我們來看看remethodizeClass
函式如何整理類資訊
static void remethodizeClass(Class cls) { category_list *cats; bool isMeta; runtimeLock.assertWriting(); isMeta = cls->isMetaClass(); // Re-methodizing: check for more categories if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) { if (PrintConnecting) { _objc_inform("CLASS: attaching categories to class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : ""); } // 附加分類 attachCategories(cls, cats, true /*flush caches*/); free(cats); } }
這個函式的主要作用是將 Category 中的方法、屬性和協議整合到類(主類或元類)中,然後通過資料欄位 data() 得到類物件裡面的資料,將所有分類的物件方法、屬性、協議,通過attachCategoryMethods函式附加到類物件的方法列表中
,而attachCategoryMethods
函式才是正在處理Category方法的
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_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; } } // 得到類物件裡面的資料 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); }
attachLists函式
裡主要的是memmove函式
和memcpy函式
,memmove函式
將原來的方法往後移動了addedCount(分類的方法數量)
個位置,memcpy函式
將分類的方法新增到原來類方法列表的位置,這樣就完美將分類的方法、協議、屬性新增到了類資訊中
void attachLists(List* const * addedLists, uint32_t addedCount) { if (addedCount == 0) return; if (hasArray()) { // many lists -> many lists uint32_t oldCount = array()->count; uint32_t newCount = oldCount + addedCount; setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); array()->count = newCount; // array()->lists 原來的方法列表 memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); // addedLists 所有分類的方法列表 memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } else if (!list&&addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; } else { // 1 list -> many lists List* oldList = list; uint32_t oldCount = oldList ? 1 : 0; uint32_t newCount = oldCount + addedCount; setArray((array_t *)malloc(array_t::byteSize(newCount))); array()->count = newCount; if (oldList) array()->lists[addedCount] = oldList; memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } }
貼一下原始碼的閱讀順序,有興趣的同學可以下載原始碼閱讀一下:
原始碼解讀順序,如下
-
objc-os.mm
-
_objc_init
-
map_images
-
map_images_nolock
-
objc-runtime-new.mm
-
_read_images
-
remethodizeClass
-
attachCategories
-
attachLists
-
realloc、memmove、 memcpy
總結:
1.通過runtime載入某個類的所有Category資料
2.把所有Category的方法、屬性、協議資料,合併到一個大陣列中,後面參與編輯的Category,會在陣列的前面。