分類(lèi)的本質(zhì)
方式一通過(guò)clang方式探究
通過(guò)clang -rewrite-objc xxxx.m -o xxxx.cpp,查看生成的xxxx.cpp∥肚模可以看到
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;
};
static struct _category_t _OBJC_$_CATEGORY_LGPerson_$_Lq __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"LGPerson",
0, // &OBJC_CLASS_$_LGPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_Lq,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_Lq,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_LGPerson_$_Lq,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGPerson_$_Lq,
};
從上面的代碼看對(duì)照著看,instance_methods對(duì)應(yīng)_CATEGORY_INSTANCE_METHODS_LGPerson纽疟,class_methods對(duì)應(yīng)_CATEGORY_CLASS_METHODS_LGPerson盛泡,protocols對(duì)應(yīng)OBJC_CATEGORY_PROTOCOLS_PROP_LIST_LGPerson夷蚊。對(duì)應(yīng)的結(jié)構(gòu)我們可以在cpp中看到构挤。
實(shí)例方法列表.方法的格式為:sel+簽名+實(shí)現(xiàn)地址。
_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_Lq __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"lq_firCreatInstanceMethod", "v16@0:8", (void *)_I_LGPerson_Lq_lq_firCreatInstanceMethod},
{(struct objc_selector *)"lq_secCreatInstanceMethod", "v16@0:8", (void *)_I_LGPerson_Lq_lq_secCreatInstanceMethod},
{(struct objc_selector *)"lq_threeCreatInstanceMethod", "v16@0:8", (void *)_I_LGPerson_Lq_lq_threeCreatInstanceMethod},
{(struct objc_selector *)"lq_fourCreatInstanceMethod", "v16@0:8", (void *)_I_LGPerson_Lq_lq_fourCreatInstanceMethod}}
};
類(lèi)方法列表
_OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_Lq __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"lq_creatClassMethod", "v16@0:8", (void *)_C_LGPerson_Lq_lq_creatClassMethod},
{(struct objc_selector *)"lq_NoClassMethod", "v16@0:8", (void *)_C_LGPerson_Lq_lq_NoClassMethod}}
};
_OBJC_CATEGORY_PROTOCOLS_$_LGPerson_$_Lq __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_LqDelegate
};
struct _protocol_t _OBJC_PROTOCOL_LqDelegate __attribute__ ((used)) = {
0,
"LqDelegate",
(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_LqDelegate,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_LqDelegate,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_LqDelegate
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_LqDelegate = &_OBJC_PROTOCOL_LqDelegate;
屬性列表
_OBJC_$_PROP_LIST_LGPerson_$_Lq __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"userName","T@\"NSString\",&,N"},
{"age","Tq,N"}}
};
方式二通過(guò)objc源碼探究
分類(lèi)在底層源碼中是一個(gè)category_t的結(jié)構(gòu)體惕鼓。
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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
}
從結(jié)構(gòu)體可以看到其中包含的元素有:
- 分類(lèi)關(guān)聯(lián)的類(lèi)名
- 分類(lèi)關(guān)聯(lián)的類(lèi)
- 實(shí)例方法列表
- 類(lèi)方法列表
- 協(xié)議列表
- 屬性列表
- 類(lèi)屬性列表
結(jié)論
分類(lèi)的本質(zhì)是一個(gè)_category_t類(lèi)型體筋现。
兩個(gè)方法列表分別存儲(chǔ)實(shí)例方法以及類(lèi)方法。
有屬性列表存放定義的屬性
協(xié)議列表存儲(chǔ)分類(lèi)中實(shí)現(xiàn)的協(xié)議箱歧。
分類(lèi)沒(méi)實(shí)例變量列表的矾飞,所以分類(lèi)不能添加實(shí)例變量的。
在分類(lèi)中定義了屬性只會(huì)有setter呀邢、getter的聲明洒沦,需要手動(dòng)去實(shí)現(xiàn)setter、getter价淌。
分類(lèi)的加載
在之前的研究中我們知道了類(lèi)的加載申眼,其中也看到了對(duì)分類(lèi)加載的處理
在methodizeClass方法中
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
分類(lèi)通過(guò)attachToClass方法加載。
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
auto it = map.find(previously);
if (it != map.end()) {
category_list &list = it->second;
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
然后在attachToClass調(diào)用attachCategories方法蝉衣,實(shí)現(xiàn)將方法列表括尸、屬性和協(xié)議從類(lèi)別附加到類(lèi)。所有的分類(lèi)是按照順序進(jìn)行加載的病毡。attachCategories中詳細(xì)的處理步驟就不做過(guò)多分析濒翻。
加載時(shí)機(jī)
attachCategories方法中是實(shí)現(xiàn)了分類(lèi)的加載的處理的,那么分類(lèi)是在什么時(shí)機(jī)加載的我們可以通過(guò)attachCategories的調(diào)用來(lái)簡(jiǎn)單的分析下。
首先我們找到attachToClass方法
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
auto it = map.find(previously);
if (it != map.end()) {
category_list &list = it->second;
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
然后找到了了load_categories_nolock方法肴焊。
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
processCatlist(_getObjc2CategoryList(hi, &count));
processCatlist(_getObjc2CategoryList2(hi, &count));
}
通過(guò)打印以及調(diào)試發(fā)現(xiàn)分類(lèi)的加載的調(diào)用順序?yàn)閘oad_images->loadAllCategories->load_categories_nolock->attachCategories前联。