關(guān)于category的文章太多了昌粤,有介紹用法的绿渣,也有介紹源碼的速种。流傳較廣的應(yīng)該算是美團(tuán)那篇深入理解Objective-C:Category。
原本我已經(jīng)不打算寫了哭当,但是在我做驗(yàn)證測(cè)試的時(shí)候發(fā)現(xiàn)了一個(gè)問(wèn)題。這個(gè)問(wèn)題在我看過(guò)的其他文章中從來(lái)沒(méi)人提起過(guò)冗澈,包括美團(tuán)那篇钦勘。我花了不少時(shí)間來(lái)研究,但因?yàn)橐黄糇C的文章都找不到亚亲,所以依然不敢肯定自己的結(jié)論彻采。
現(xiàn)在我只能認(rèn)為是由于runtime的更新導(dǎo)致了不一致腐缤。這篇文章,只為記錄下這個(gè)問(wèn)題肛响。歡迎對(duì)此有更深入理解的小伙伴給我留言岭粤,不勝感激。
本文不會(huì)對(duì)category做系統(tǒng)的分析特笋,如果對(duì)category不太了解的請(qǐng)先閱讀美團(tuán)那篇文章剃浇。
結(jié)論
首先看看其他文章中給出的一個(gè)結(jié)論:
- 分類是在運(yùn)行時(shí)被附加到相關(guān)類上的
我測(cè)試下來(lái)的結(jié)論是:
- 對(duì)系統(tǒng)提供的類做分類,分類是在運(yùn)行時(shí)附加到相關(guān)類上的
- 對(duì)自己創(chuàng)建的類做分類猎物,分類是在編譯期附加到相關(guān)類上的
在其他文章中虎囚,根本沒(méi)有這樣的區(qū)分,分類全部都是在運(yùn)行時(shí)附加上去的蔫磨,附加時(shí)的調(diào)用棧也很清晰:
過(guò)程
最開(kāi)始我對(duì)其他文章給出的結(jié)論是深信不疑的淘讥,因?yàn)樗械奈恼露际沁@么說(shuō)的。我只是想寫代碼做一下驗(yàn)證堤如。
為了驗(yàn)證那個(gè)結(jié)論蒲列,我創(chuàng)建了這些類:
- 對(duì)NSObject和NSViewController創(chuàng)建的分類
- 對(duì)ZNObject創(chuàng)建的分類,ZNObject繼承于NSObject
- 對(duì)ZNViewController創(chuàng)建的分類搀罢,ZNViewController繼承于NSViewController
我在attachCategories()方法里面添加了一點(diǎn)代碼蝗岖,用于打印出class和category,代碼如下:
我不會(huì)分析這個(gè)方法魄揉,只需要注意我添加了()的那一行*
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);
(*) printf("class name is: %s, category name is: %s\n", cls->mangledName(), (*(entry.cat)).name);
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);
}
運(yùn)行之前剪侮,我預(yù)期上面寫的4個(gè)分類都可以打印出來(lái),格式應(yīng)該是這樣的:
預(yù)期結(jié)果洛退,這4行應(yīng)該穿插在結(jié)果當(dāng)中:
...
class name is: NSObject, category name is: NSObjAddition
...
class name is: NSViewController, category name is: NSVCAddition
...
class name is: ZNObject, category name is: MyObjAddition
...
class name is: ZNViewController, category name is: MyVCAddition
...
結(jié)果當(dāng)然不是這樣的瓣俯,不然我也不會(huì)寫這篇文章了:
前2個(gè)分類都順利找到了,但是后兩個(gè)分類卻搜索不到兵怯。
為了更清晰一點(diǎn)彩匕,我把那行printf修改了一下,這樣可以過(guò)濾掉無(wú)關(guān)的信息:
if (!strcmp((*(entry.cat)).name, "NSObjAddition")
|| !strcmp((*(entry.cat)).name, "MyObjAddition")
|| !strcmp((*(entry.cat)).name, "NSVCAddition")
|| !strcmp((*(entry.cat)).name, "MyVCAddition")) {
printf("class name is: %s, category name is: %s\n", cls->mangledName(), (*(entry.cat)).name);
}
結(jié)果當(dāng)然還是一樣的:
一共只有兩行媒区,而不是預(yù)期的四行驼仪。
我一度以為是在類首次被調(diào)用時(shí),通過(guò)realizeClass()方法調(diào)用methodizeClass()再調(diào)用attachCategories()進(jìn)行的袜漩,然而即使我主動(dòng)調(diào)用了category中的方法绪爸,還是不會(huì)有信息打印出來(lái)。
所以問(wèn)題就來(lái)了宙攻,上圖中2和3兩塊的category究竟是什么時(shí)候被附加到類上的呢奠货?
猜測(cè):既然在運(yùn)行期找不到附加的過(guò)程,那是不是編譯期就已經(jīng)做完這個(gè)操作了呢座掘?
為了驗(yàn)證這個(gè)猜測(cè)递惋,我在之前創(chuàng)建的ZNObject+MyObjAddition分類中添加了一個(gè)方法:
// ZNObject+MyObjAddition.h
#import "ZNObject.h"
@interface ZNObject (MyObjAddition)
- (void)printMyObjAddition;
@end
// ZNObject+MyObjAddition.m
#import "ZNObject+MyObjAddition.h"
@implementation ZNObject (MyObjAddition)
- (void)printMyObjAddition {
NSLog(@"MyObjAddition");
}
@end
接著在_objc_init()方法的最開(kāi)始添加了一個(gè)斷點(diǎn)柔滔,然后獲取了一下ZNObject類的方法列表:
可以看到分類的方法這個(gè)時(shí)候已經(jīng)在baseMethodList中了,所以說(shuō)在編譯期結(jié)束的時(shí)候分類就已經(jīng)附加完成了萍虽。
我又對(duì)NSObject做了相同的測(cè)試睛廊,結(jié)果是NSObject+NSObjAddition分類的方法在這個(gè)時(shí)候并沒(méi)有被添加進(jìn)來(lái)。確實(shí)就是在attachCategories()方法中被添加的杉编。
所以才有了文章最開(kāi)始我放出的那個(gè)結(jié)論超全,這里再列一下作為本文的結(jié)尾吧:
- 對(duì)系統(tǒng)提供的類做分類,分類是在運(yùn)行時(shí)附加到相關(guān)類上的
- 對(duì)自己創(chuàng)建的類做分類王财,分類是在編譯期附加到相關(guān)類上的
這就是我測(cè)試下來(lái)的結(jié)論卵迂,這個(gè)結(jié)論看起來(lái)實(shí)在是太奇怪了。也完全找不到相關(guān)文章佐證绒净,所以见咒。。挂疆。
如果有同學(xué)對(duì)此有更深入的研究改览,請(qǐng)一定留言,不甚感激缤言!
ps:這個(gè)過(guò)程也告訴我一個(gè)信息宝当,即使幾乎所有人都那么說(shuō),結(jié)果也可能是錯(cuò)誤的胆萧,紙上得來(lái)終覺(jué)淺呀~