category簡介
category是Objective-C 2.0之后添加的語言特性,category的主要作用是為已經(jīng)存在的類添加方法。除此之外,apple還推薦了category的另外兩個使用場景1
- 可以把類的實現(xiàn)分開在幾個不同的文件里面。這樣做有幾個顯而易見的好處,a)可以減少單個文件的體積 b)可以把不同的功能組織到不同的category里 c)可以由多個開發(fā)者共同完成一個類 d)可以按需加載想要的category 等等。
- 聲明私有方法
category真面目
所有的OC類和對象,在runtime層都是用struct表示的,category也不例外,在runtime層,category用結(jié)構(gòu)體category_t(在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;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
成員變量
1)、類的名字(name)
2)、類(cls)
3)、category中所有給類添加的實例方法的列表(instanceMethods)
4)、category中所有添加的類方法的列表(classMethods)
5)、category實現(xiàn)的所有協(xié)議的列表(protocols)
6)、category中添加的所有屬性(instanceProperties)
方法
method_list_t *methodsForMeta(bool isMeta)
根據(jù)傳入是是不是元類來返回響應(yīng)的值。
property_list_t *propertiesForMeta(bool isMeta)
這個就是判斷是否是元類返回響應(yīng)的屬性,元類是沒有屬性的。
代碼重編譯
我們先寫個category 類,我們用clang -rewrite-objc xx.m 編譯這個catergory
#import <Foundation/Foundation.h>
@interface CategoryObject : NSObject
@end
@interface CategoryObject(MyAddition)
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *value;
- (void)printName;
-(void)printValue;
@end
@interface CategoryObject(MyAddition2)
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *age;
@property(nonatomic, copy) NSString *address;
- (void)printName;
-(void)printAge;
-(void)printAddress;
@end
#import "CategoryObject.h"
@implementation CategoryObject
- (void)printName
{
NSLog(@"%@",@"CategoryObject");
}
@end
@implementation CategoryObject(MyAddition)
- (void)printName
{
NSLog(@"printName %@",@"MyAddition");
}
-(void)printValue{
NSLog(@"printValue %@",@"MyAddition");
}
@end
@implementation CategoryObject(MyAddition2)
-(void)printAge{
NSLog(@"printAge %@",@"MyAddition2");
}
-(void)printAddress{
NSLog(@"printAddress %@",@"MyAddition2");
}
- (void)printName
{
NSLog(@"printName %@",@"MyAddition2");
}
@end
這里我們定義了兩個category ,并且每一個category中都有方法和屬性。
重新編譯后的文件很多,我們就摘抄與我們有關(guān)的部分。
我們寫的所有代碼都是在文件最后面
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printName},
{(struct objc_selector *)"printValue", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printValue}}
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name","T@\"NSString\",C,N"},
{"value","T@\"NSString\",C,N"}}
};
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_CategoryObject;
static struct _category_t _OBJC_$_CATEGORY_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASS_$_CategoryObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CategoryObject_$_MyAddition,
};
static void OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition(void ) {
_OBJC_$_CATEGORY_CategoryObject_$_MyAddition.cls = &OBJC_CLASS_$_CategoryObject;
}
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition2 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"printAge", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printAge},
{(struct objc_selector *)"printAddress", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printAddress},
{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printName}}
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[3];
} _OBJC_$_PROP_LIST_CategoryObject_$_MyAddition2 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
3,
{{"name","T@\"NSString\",C,N"},
{"age","T@\"NSString\",C,N"},
{"address","T@\"NSString\",C,N"}}
};
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_CategoryObject;
static struct _category_t _OBJC_$_CATEGORY_CategoryObject_$_MyAddition2 __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASS_$_CategoryObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition2,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CategoryObject_$_MyAddition2,
};
static void OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition2(void ) {
_OBJC_$_CATEGORY_CategoryObject_$_MyAddition2.cls = &OBJC_CLASS_$_CategoryObject;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition,
(void *)&OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition2,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS_$_CategoryObject,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_CategoryObject_$_MyAddition,
&_OBJC_$_CATEGORY_CategoryObject_$_MyAddition2,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
一點點看
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printName},
{(struct objc_selector *)"printValue", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printValue}}
};
這里生成一個靜態(tài)的struct 名字叫OBJC_MyAddition,并且初始化該結(jié)構(gòu)體;
這個結(jié)構(gòu)體有三個變量
entsize 代表的是一個 struct _objc_method 的大小
method_count 代表category中有幾個方法
method_list[2];是個數(shù)組,裝的方法名字。
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name","T@\"NSString\",C,N"},
{"value","T@\"NSString\",C,N"}}
};
屬性也是和method 一樣的生成方式
這個結(jié)構(gòu)體有三個變量
entsize 代表的是一個 struct _prop_t 的大小
count_of_properties 代表category中有幾個屬性
prop_list[2];是個數(shù)組,裝的屬性
static struct _category_t _OBJC_$_CATEGORY_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASS_$_CategoryObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CategoryObject_$_MyAddition,
};
這里就是給_category_t 結(jié)構(gòu)體賦值,結(jié)構(gòu)體名字規(guī)則是 文件頭 + 類名+ 類別名。不過這里的 classMethods 和 protocols 都是0 ,因為我們沒有給類別增加這些東西。所以都是0.
static void OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition(void ) {
_OBJC_$_CATEGORY_CategoryObject_$_MyAddition.cls = &OBJC_CLASS_$_CategoryObject;
}
我們看category的 cls變量沒有賦值。這里給出一個單獨的函數(shù)對cls進行賦值。
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_CategoryObject_$_MyAddition,
&_OBJC_$_CATEGORY_CategoryObject_$_MyAddition2,
};
這里保存一個_category_t 數(shù)組。
大概上面的代碼看完了
數(shù)據(jù)段信息
有三個信息,一個是含有分類的類列表,一個是分類列表,還有一個分類關(guān)聯(lián)對象列表
含有分類的的類列表
[Obj1,obj2,obj3]
分類列表
[
category1:[
method
prop
],
category1:[
method
prop
]
]
分類關(guān)聯(lián)對象列表
[
{
category:obj
}
]
編譯器做了啥事情呢?
1)、首先編譯器生成了實例方法列表
2)、其次,編譯器生成了category本身
3)、最后,編譯器在DATA段下的objc_catlist section里保存了一個大小為1的category_t的數(shù)組L_OBJC_LABELCATEGORY$(當(dāng)然,如果有多個category,會生成對應(yīng)長度的數(shù)組_),用于運行期category的加載。
追本溯源
Objective-C的運行是依賴OC的runtime的,而OC的runtime和其他系統(tǒng)庫一樣,是OS X和iOS通過dyld動態(tài)加載的。
對于OC運行時,入口方法如下(在objc-os.mm文件中):
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
lock_init();
exception_init();
// Register for unmap first, in case some +load unmaps something
_dyld_register_func_for_remove_image(&unmap_image);
dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}
這個函數(shù)最終會調(diào)用到_read_images 方法中
怎么知道的呢?我們打斷點調(diào)試下不就知道了。我們選擇symbolic breakpoint 斷點,進行調(diào)試
截圖如下

這個_read_images方法中有有關(guān)category相關(guān)增加方法。
我們摘錄相關(guān)部分
void _read_images(header_info **hList, uint32_t hCount)
{
···
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
···
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
···
// Fix up @selector references
···
// Discover protocols. Fix up protocol refs.
···
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
···
// Realize non-lazy classes (for +load methods and static instances)
···
// Realize newly-resolved future classes, in case CF manipulates them
···
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
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
/* || 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);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
···
// Category discovery MUST BE LAST to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
···
// Print preoptimization statistics
}
注意,_getObjc2CategoryList 方法是從數(shù)據(jù)段讀取__objc_catlist部分?jǐn)?shù)據(jù)列表
GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist");
這個函數(shù)的基本結(jié)構(gòu)就是這個樣子。分的模塊很明確
我們就看我們category部分
1 獲取category 列表list
2遍歷category list 中的每一個category
3.獲取category 的cls.要是category 沒有設(shè)置cls 就繼續(xù)下一個
4.這里判斷cat 是否有實例方法,協(xié)議或者屬性。有的話就調(diào)用下
addUnattachedCategoryForClass 方法,判斷cls 實現(xiàn)的話,就調(diào)用remethodizeClass 方法
5.再判斷category 是否有類方法或者協(xié)議。有的話也調(diào)用addUnattachedCategoryForClass 方法。在檢測元類是否實現(xiàn)。調(diào)用下remethodizeClass 方法。
這里有兩個關(guān)鍵方法addUnattachedCategoryForClass 和remethodizeClass 。
分別看
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
1.調(diào)用unattachedCategories() 函數(shù)生成一個NXMapTable * cats。在這里cats 又是全局對象,只有一個
static NXMapTable *unattachedCategories(void)
{
runtimeLock.assertWriting();
static NXMapTable *category_map = nil;
if (category_map) return category_map;
// fixme initial map size
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
return category_map;
}
這里不看這個對象具體怎么生成的了。
2.我們從這個單例對象中查找cls ,獲取一個category_list *list列表。
3 要是沒有l(wèi)ist 指針。那么我們就生成一個category_list 空間。
4.要是有l(wèi)ist 指針,那么就在該指針的基礎(chǔ)上再分配出category_list 大小的空間。
5.在這新分配好的空間,將這個cat 和catHeader 寫入。
6.將數(shù)據(jù)插入到cats 中。key 是cls 值是list
數(shù)據(jù)結(jié)構(gòu)是這樣子的

一個全局map ,cls 是key value 是個list (相當(dāng)于數(shù)組)
接下來看static void remethodizeClass(Class cls) 方法
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);
}
}
這個類很簡單,就是獲取下cats = unattachedCategoriesForClass(cls, false/not realizing/)**
要是有cats ,那么久調(diào)用 attachCategories(cls, cats, true /flush caches/);方法
看看
** static void
attachCategories(Class cls, category_list cats, bool flush_caches)* 方法
// 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_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);
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);
}
從這里方法的注釋上看,我們知道這個方法是連接方法列表,屬性,協(xié)議到class。這里category 有個排序問題,誰先加載誰在前面。
我們看源碼分析
- 給category 的方法屬性和協(xié)議分配空間。
- 獲取方法屬性協(xié)議 都存放在剛才分配的空間里。
3 .獲取cls 的的bits 指針 class_rw_t。
4.調(diào)用下prepareMethodLists 方法。(沒有改變啥東東)
5.連接方法列表
6.要是需要刷新緩存,刷新下。
7.連接屬性
8.連接協(xié)議
這里看6 7 8 的連接。其實method 屬性和協(xié)議都是繼承l(wèi)ist_array_tt 類
class method_array_t :
public list_array_tt<method_t, method_list_t>
class property_array_t :
public list_array_tt<property_t, property_list_t>
class protocol_array_t :
public list_array_tt<protocol_ref_t, protocol_list_t>
看看list_array_tt方法的attachLists 函數(shù)實現(xiàn)
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;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
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]));
}
}
假設(shè)這里hasArray中有值。操作如圖

大概流程如上圖
這里需要知道
1)、category的方法沒有“完全替換掉”原來類已經(jīng)有的方法,也就是說如果category和原來類都有methodA,那么category附加完成之后,類的方法列表里會有兩個methodA。
2)、category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的后面,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應(yīng)名字的方法,就會罷休_,殊不知后面可能還有一樣名字的方法。
3)、不單單是method 是這樣的邏輯,屬性和協(xié)議也是一樣的。
旁枝末葉-category和+load方法
我們知道,在類和category中都可以有+load方法,那么有兩個問題:
1)、在類的+load方法調(diào)用的時候,我們可以調(diào)用category中聲明的方法么?
2)、這么些個+load方法,調(diào)用順序是咋樣的呢?
我們創(chuàng)建了CategoryLoad方法 和他的兩個category1 和category2

分別在這三個.m 文件中實現(xiàn)load 方法
#import "CategoryLoad.h"
@implementation CategoryLoad
+(void)load{
NSLog(@"CategoryLoad");
}
@end
#import "CategoryLoad+Category1.h"
@implementation CategoryLoad (Category1)
+(void)load{
NSLog(@"CategoryLoad+Category1");
}
@end
#import "CategoryLoad+Category2.h"
@implementation CategoryLoad (Category2)
+(void)load{
NSLog(@"CategoryLoad+Category2");
}
@end
我們在scheme中添加參數(shù)

這個時候的編譯資源是

順序是 category2 category category1
結(jié)果是
objc[39683]: LOAD: class 'CategoryLoad' scheduled for +load
objc[39683]: LOAD: category 'CategoryLoad(Category2)' scheduled for +load
objc[39683]: LOAD: category 'CategoryLoad(Category1)' scheduled for +load
objc[39683]: LOAD: +[CategoryLoad load]
2018-04-18 17:01:08.938065+0800 CategoryLoadTest[39683:11252165] CategoryLoad
objc[39683]: LOAD: +[CategoryLoad(Category2) load]
2018-04-18 17:01:08.939816+0800 CategoryLoadTest[39683:11252165] CategoryLoad+Category2
objc[39683]: LOAD: +[CategoryLoad(Category1) load]
2018-04-18 17:01:08.940287+0800 CategoryLoadTest[39683:11252165] CategoryLoad+Category1
所以,對于上面兩個問題,答案是很明顯的:
1)、可以調(diào)用,因為附加category到類的工作會先于+load方法的執(zhí)行
2)、+load的執(zhí)行順序是先類,后category,而category的+load執(zhí)行順序是根據(jù)編譯順序決定的。
當(dāng)我們調(diào)整下編譯順序

編譯結(jié)果是
objc[39907]: LOAD: class 'CategoryLoad' scheduled for +load
objc[39907]: LOAD: category 'CategoryLoad(Category1)' scheduled for +load
objc[39907]: LOAD: category 'CategoryLoad(Category2)' scheduled for +load
objc[39907]: LOAD: +[CategoryLoad load]
2018-04-18 17:10:24.589178+0800 CategoryLoadTest[39907:11261059] CategoryLoad
objc[39907]: LOAD: +[CategoryLoad(Category1) load]
2018-04-18 17:10:24.590856+0800 CategoryLoadTest[39907:11261059] CategoryLoad+Category1
objc[39907]: LOAD: +[CategoryLoad(Category2) load]
2018-04-18 17:10:24.591858+0800 CategoryLoadTest[39907:11261059] CategoryLoad+Category2
輸出變了
對于+load的執(zhí)行順序是這樣,但是對于“覆蓋”掉的方法,則會先找到最后一個編譯的category里的對應(yīng)方法。
方法覆蓋找回
我們知道如何我們用category 把類中的方法給覆蓋掉了。那么我們調(diào)用這個方法會是category中的方法,那么我們想調(diào)用類中的方法怎么辦呢?
從上面的分析我們知道,我們加載的category 方法的時候是把category方法插入到類方法的前面,這樣調(diào)用方法就調(diào)用到category方法。想調(diào)用類方法,我們就接著遍歷唄。知道跟的那個方法就是類方法了。
Class currentClass= [CategoryObject class];
CategoryObject *obj = [[CategoryObject alloc]init];
if (currentClass) {
unsigned int methodCount;
Method *methodList = class_copyMethodList(currentClass, &methodCount);
IMP lastImp = NULL;
SEL lastSel = NULL;
for (NSInteger i = 0; i < methodCount; i++) {
Method method = methodList[i];
NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))
encoding:NSUTF8StringEncoding];
if ([@"printName" isEqualToString:methodName]) {
lastImp = method_getImplementation(method);
lastSel = method_getName(method);
typedef void (*fn)(id,SEL);
if (lastImp != NULL) {
fn f = (fn)lastImp;
f(obj,lastSel);
}
}
}
free(methodList);
}
這里我把所有的方法都打印出來了
結(jié)果
2018-04-18 17:25:12.488418+0800 Category[40299:11273773] printName MyAddition2
2018-04-18 17:25:12.488671+0800 Category[40299:11273773] printName MyAddition
2018-04-18 17:25:12.488875+0800 Category[40299:11273773] CategoryObject
這個結(jié)果說明了最后的一個方法肯定是類的方法。