Category的本質(zhì)<二>load,initialize方法
Category的本質(zhì)<三>關(guān)聯(lián)對(duì)象
一 寫在開頭
Category大家應(yīng)該用過(guò)漓拾,它主要是用在為對(duì)象的不同類型的功能分塊人断,比如說(shuō)人這個(gè)對(duì)象肴捉,我們可以為其創(chuàng)建三個(gè)分類痢站,分別對(duì)應(yīng)學(xué)習(xí)吻贿,工作鳍怨,休息呻右。
下面創(chuàng)建了一個(gè)Person類和兩個(gè)Person類的分類。分別是Person+Test和Person+Eat京景。這三個(gè)類中各有一個(gè)方法窿冯。
//Person類
- (void)run;
- (void)run{
NSLog(@"run");
}
//Person+Test分類
- (void)test;
- (void)test{
NSLog(@"test");
}
//Person+Eat分類
- (void)eat;
- (void)eat{
NSLog(@"eat");
}
當(dāng)我們需要使用這些分類的時(shí)候只需要引入這些分類的頭文件即可:
#import "Person+Test.h"
#import "Person+Eat.h"
Person *person = [[Person alloc] init];
[person run];
[person test];
[person eat];
我們都知道,函數(shù)調(diào)用的本質(zhì)是消息機(jī)制确徙。[person run]
的本質(zhì)就是objc_mgs(person, @selector(run))
,這個(gè)很好理解醒串,由于對(duì)象方法是存放在類對(duì)象中的,所以向person對(duì)象發(fā)送消息就是通過(guò)person對(duì)象的isa指針找到其類對(duì)象鄙皇,然后在類對(duì)象中找到這個(gè)對(duì)象方法芜赌。
[person test]
和[person run]
都是調(diào)用分類的對(duì)象方法,本質(zhì)應(yīng)該一樣伴逸。[person test]
的本質(zhì)就是objc_mgs(person缠沈, @selector(test))
,給實(shí)例對(duì)象發(fā)送消息错蝴,person對(duì)象通過(guò)自己的isa指針找到類對(duì)象洲愤,然后在自己的類對(duì)象中查找這個(gè)實(shí)例方法,那么問(wèn)題來(lái)了顷锰,person類對(duì)象中有沒(méi)有存儲(chǔ)分類中的這個(gè)對(duì)象方法呢柬赐?Person+Test這個(gè)分類會(huì)不會(huì)有自己的分類的類對(duì)象,將分類的對(duì)象方法存儲(chǔ)在這個(gè)類對(duì)象中呢官紫?
我們要清楚的一點(diǎn)是每個(gè)類只有一個(gè)類對(duì)象肛宋,不管這個(gè)類有沒(méi)有分類州藕。所以分類中的對(duì)象方法也就存儲(chǔ)在Person類的類對(duì)象中。后面我們會(huì)通過(guò)源碼證實(shí)這一點(diǎn)酝陈。
二 底層結(jié)構(gòu)
我們?cè)诘谝徊糠种v了床玻,分類中的對(duì)象方法和類方法最終會(huì)合并到類中,分類中的對(duì)象方法合并到類的類對(duì)象中沉帮,分類中的類方法合并到類的元類對(duì)象中锈死。那么這個(gè)合并是什么時(shí)候發(fā)生的呢?是在編譯器編譯器就幫我們合并好了嗎穆壕?實(shí)際上是在運(yùn)行期馅精,進(jìn)行的合并。
下面我們通過(guò)將Objective-c的代碼轉(zhuǎn)化為c++的源碼窺探一下Category的底層結(jié)構(gòu)粱檀。我們?cè)诿钚羞M(jìn)入到存放Person+Test.m這個(gè)文件的文件夾中洲敢,然后在命令行輸入clang -rewrite-objc Person+Test.m
,這樣Person+Test.m這個(gè)文件就被轉(zhuǎn)化為了c++的源碼Person+Test.cpp。
我們打開這個(gè).cpp文件茄蚯,由于這個(gè)文件非常長(zhǎng)压彭,所以我們直接拖到最下面,找到_category_t
這個(gè)結(jié)構(gòu)體渗常。這個(gè)結(jié)構(gòu)體就是每一個(gè)分類的結(jié)構(gòu):
struct _category_t {
const char *name; //類名
struct _class_t *cls;
const struct _method_list_t *instance_methods; //對(duì)象方法列表
const struct _method_list_t *class_methods; //實(shí)例方法列表
const struct _protocol_list_t *protocols; //協(xié)議列表
const struct _prop_list_t *properties; //屬性列表
};
我們接著往下找到這個(gè)結(jié)構(gòu)體的初始化:
static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,
0,
0,
};
通過(guò)結(jié)構(gòu)體名稱_OBJC_$_CATEGORY_Person_$_Test
我們可以知道這是Person+Test這個(gè)分類的初始化壮不。類名對(duì)應(yīng)的是"Person"
,對(duì)象方法列表這個(gè)結(jié)構(gòu)體對(duì)應(yīng)的是&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test
,類方法列表這個(gè)結(jié)構(gòu)體對(duì)應(yīng)的是&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test
,其余的初始化都是空。
然后我們找到&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test
這個(gè)結(jié)構(gòu)體:
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_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_Test_test}}
};
可以看到這個(gè)結(jié)構(gòu)體中包含一個(gè)對(duì)象方法test皱碘,這正是Person+Test這個(gè)分類中的對(duì)象方法询一。
然后我們?cè)僬业?code>&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test這個(gè)結(jié)構(gòu)體:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_Person_Test_test2}}
};
同樣可以看到這個(gè)結(jié)構(gòu)體,它包含一個(gè)類方法test2癌椿,這個(gè)同樣是Person+Test中的類方法健蕊。
三 利用runtime進(jìn)行合并
由于整個(gè)合并的過(guò)程是通過(guò)runtime進(jìn)行實(shí)現(xiàn)的,所以我們要了解這個(gè)過(guò)程就要通過(guò)查看runtime源碼去了解踢俄。下面是查看runtime源碼的過(guò)程:
- 1.找到objc-os.mm這個(gè)文件缩功,這個(gè)文件是runtime的入口文件。
- 2.在objc-os.mm中找到
_objc_init(void)
這個(gè)方法都办,這個(gè)方法是運(yùn)行時(shí)的初始化嫡锌。 - 3.在
_objc_init(void)
中會(huì)調(diào)用_dyld_objc_notify_register(&map_images, load_images, unmap_image);
,這個(gè)函數(shù)會(huì)傳入map_images
這個(gè)參數(shù),我們點(diǎn)進(jìn)這個(gè)參數(shù)琳钉。 - 4.點(diǎn)擊進(jìn)去
map_images
我們發(fā)現(xiàn)其中調(diào)用了map_images_nolock(count, paths, mhdrs);
這個(gè)函數(shù)势木,我們點(diǎn)進(jìn)這個(gè)函數(shù)。 - 5.
map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[])
這個(gè)函數(shù)非常長(zhǎng)歌懒,我們直接拉到這個(gè)函數(shù)最下面啦桌,找到_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
這個(gè)函數(shù),點(diǎn)擊進(jìn)去歼培。 - 6.
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
這個(gè)方法大概就是讀取模塊的意思了震蒋。
這個(gè)函數(shù)也是非常長(zhǎng),我們大概在中間位置找到了這樣一行注釋
// Discover categories.
這個(gè)基本上就是我們要找的處理Category的模塊了躲庄。
我們?cè)谶@行注釋下面找到這幾行代碼:
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA()); //class的ISA指針指向的是元類對(duì)象
}
這個(gè)代碼里面有一個(gè)關(guān)鍵函數(shù)remethodizeClass
,通過(guò)函數(shù)名我們大概猜測(cè)這個(gè)方法是重新組織類中的方法查剖,如果傳入的是類,則重新組織對(duì)象方法噪窘,如果傳入的是元類笋庄,則重新組織類方法。
- 7.然后我們點(diǎn)進(jìn)這個(gè)方法里面查看:
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);
}
}
我們看到這段代碼的核心是調(diào)用了attachCategories(cls, cats, true /*flush caches*/);
這個(gè)方法倔监。這個(gè)方法中傳入了一個(gè)類cls和所有的分類cats直砂。
- 8.我們點(diǎn)進(jìn)
attachCategories(cls, cats, true /*flush caches*/);
這個(gè)方法。這個(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
//方法數(shù)組
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//屬性數(shù)組
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//協(xié)議數(shù)組
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--) {
//取出某個(gè)分類
auto& entry = cats->list[i];
//確定是對(duì)象方法還是類方法
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;
}
}
//得到類對(duì)象里面的數(shù)據(jù)
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//將所有分類的對(duì)象方法静暂,附加到類對(duì)象的方法列表中
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
//將所有分類的協(xié)議,附加到類對(duì)象的協(xié)議列表中
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
-
bool isMeta = cls->isMetaClass();
判斷是類還是元類谱秽。 - 創(chuàng)建總的方法數(shù)組洽蛀,屬性數(shù)組,協(xié)議數(shù)組
//方法數(shù)組
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//屬性數(shù)組
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//協(xié)議數(shù)組
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
這里mlists疟赊,proplists郊供,protolists都是用兩個(gè)修飾的,說(shuō)明是申請(qǐng)了一個(gè)二維數(shù)組近哟。這三個(gè)二維數(shù)組里面的一級(jí)對(duì)象分別是方法列表驮审,屬性列表,以及協(xié)議列表吉执。由于每一個(gè)分類Category都有一個(gè)方法列表疯淫,一個(gè)屬性列表,一個(gè)協(xié)議列表戳玫,方法列表中裝著這個(gè)分類的方法峡竣,屬性列表中裝著這個(gè)分類的屬性。所以mlists也就是裝著所有分類的所有方法量九。
- 給前面創(chuàng)建的數(shù)組賦值
while (i--) {
//取出某個(gè)分類
auto& entry = cats->list[i];
//確定是對(duì)象方法還是類方法
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;
}
}
這段代碼就很清楚了适掰,通過(guò)一個(gè)while循環(huán)遍歷所有的分類,然后獲取該分類的所有方法荠列,賦值給前面創(chuàng)建的大數(shù)組类浪。
-
rw = cls->data();
得到類對(duì)象里面的所有數(shù)據(jù)。 -
rw->methods.attachLists(mlists, mcount);
將所有分類的方法肌似,附加到類的方法列表中费就。 - 9.我們點(diǎn)進(jìn)這個(gè)方法里面看看具體的實(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]));
}
}
傳進(jìn)來(lái)的這個(gè)addedLists參數(shù)就是前面得到的這個(gè)類的所有分類的對(duì)象方法或者類方法,而addedCount就是addedLists這個(gè)數(shù)組的個(gè)數(shù)川队。假設(shè)這個(gè)類有兩個(gè)分類力细,且每個(gè)分類有兩個(gè)方法睬澡,那么addedLists的結(jié)構(gòu)大概就應(yīng)該是這樣的:
[
[method, method]
[method, method]
]
addedCount = 2
我們看一下這個(gè)類的方法列表之前的結(jié)構(gòu):
所以oldCount = 1
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
這一句是重新分配內(nèi)存,由于要把分類的方法合并進(jìn)來(lái)眠蚂,所以以前分配的內(nèi)存就不夠了煞聪,重新分配后的內(nèi)存:
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memmove這個(gè)函數(shù)是把第二個(gè)位置的對(duì)象移動(dòng)到第一個(gè)位置。這里也就是把這個(gè)類本來(lái)的方法列表移動(dòng)到第三個(gè)位置逝慧。
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
memcpy這個(gè)函數(shù)是把第二個(gè)位置的對(duì)象拷貝到第一個(gè)位置昔脯,也就是把a(bǔ)ddedLists拷貝到第一個(gè)位置,拷貝之后的內(nèi)存應(yīng)該是這樣的:
至此就把分類中的方法列表合并到了類的方法列表中笛臣。
通過(guò)上面的合并過(guò)程我們也明白了云稚,當(dāng)分類和類中有同樣的方法時(shí),類中的方法并沒(méi)有被覆蓋沈堡,只是分類的方法被放在了類的方法前面静陈,導(dǎo)致先找到了分類的方法,所以分類的方法就被執(zhí)行了诞丽。
四 總結(jié)
1.通過(guò)runtime加載某個(gè)類的所有Category數(shù)據(jù)窿给。
2.把所有Category的方法,屬性率拒,協(xié)議數(shù)據(jù)合并到一個(gè)大數(shù)組中崩泡,后參與編譯的Category數(shù)據(jù),會(huì)存放在數(shù)組的前面猬膨。
3.將合并后的分類數(shù)據(jù)(方法角撞,屬性,協(xié)議)勃痴,插入到類原來(lái)數(shù)據(jù)的前面谒所。
Category的本質(zhì)<二>load,initialize方法