前言
为了了解分类,写了下面的TestA类和它的三个分类,上图是三个分类文件的编译顺序。 TestA类中有一个testA的实例方法和一个load类方法,TestA (One)和TestA (Two)中同样实现了这两个方法,TestA (Three)中只实现了load方法。TestA(One)中还声明了一个属性oneStr。
@interface TestA : NSObject
- (void)testA;
@end
@implementation TestA
+ (void)load {
NSLog(@"TestA load");
}
- (void)testA {
NSLog(@"TestA method name : %s", __func__);
}
@end
@interface TestA (One)
@property (nonatomic, copy) NSString *oneStr;
- (void)testA;
@end
@implementation TestA (One)
+ (void)load {
NSLog(@"TestA+One load");
}
- (void)testA {
NSLog(@"TestA+One method name : %s", __func__);
}
@end
@interface TestA (Two)
- (void)testA;
@end
@implementation TestA (Two)
+ (void)load {
NSLog(@"TestA+Two load");
}
- (void)testA {
NSLog(@"TestA+Two method name : %s", __func__);
}
@end
@interface TestA (Three)
@end
@implementation TestA (Three)
+ (void)load {
NSLog(@"TestA+Three load");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestA *testA = [[TestA alloc] init];
[testA testA];
}
return 0;
}
一、分类的本质
通过clang命令把分类的TestA+One.m转为c++的.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 /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_TestA_$_One __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{
{
"oneStr","T@\"NSString\",C,N"}}
};
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_TestA_$_One __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{
{
(struct objc_selector *)"testA", "v16@0:8", (void *)_I_TestA_One_testA}}
};
可从中得出_category_t的结构体,对应于objc源码为
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; //实现属性
struct property_list_t *_classProperties; //类属性
}
从.cpp中也可以看到分类One中的属性oneStr虽在属性列表_OBJC_KaTeX parse error: Expected group after '_' at position 17: …PROP_LIST_TestA_̲One中存在,但是分类实例方法列表_OBJCKaTeX parse error: Expected group after '_' at position 33: …E_METHODS_TestA_̲_One中并没有oneStr/setOneStr方法。可知分类的属性只是声明,需要自己去实现setter/getter。
Xcode中也会有警告提示。
二、分类加载
2.1 线索一:从map_images引出load_images中的loadAllCategories,再到load_categories_nolock中的attachCategories
在前面文章分析map_images方法源码时,在懒加载代码前有一段关于分类的代码
//仅在完成初始分类附件后才执行此操作。对于启动时出现的类别,将发现推迟到对_dyld_objc_notify_register的调用完成后的第一个load_images调用之后。
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
从注释中的信息找到load_images中以下代码
//didInitialAttachCategories初始为flase,didCallDyldNotifyRegister在objc_init最后初设为了true
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
static void loadAllCategories() {
mutex_locker_t lock(runtimeLock);
for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
load_categories_nolock(hi);
}
}
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}