分类的本质
方式一通过clang方式探究
通过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,
};
从上面的代码看对照着看,instance_methods对应_CATEGORY_INSTANCE_METHODS_LGPerson,class_methods对应_CATEGORY_CLASS_METHODS_LGPerson,protocols对应OBJC_CATEGORY_PROTOCOLS_PROP_LIST_LGPerson。对应的结构我们可以在cpp中看到。
实例方法列表.方法的格式为:sel+签名+实现地址。
_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}}
};
类方法列表
_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"}}
};
方式二通过objc源码探究
分类在底层源码中是一个category_t的结构体。
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;
}
}
从结构体可以看到其中包含的元素有:
- 分类关联的类名
- 分类关联的类
- 实例方法列表
- 类方法列表
- 协议列表
- 属性列表
- 类属性列表
结论
分类的本质是一个_category_t类型体。
两个方法列表分别存储实例方法以及类方法。
有属性列表存放定义的属性
协议列表存储分类中实现的协议。
分类没实例变量列表的,所以分类不能添加实例变量的。
在分类中定义了属性只会有setter、getter的声明,需要手动去实现setter、getter。
分类的加载
在之前的研究中我们知道了类的加载,其中也看到了对分类加载的处理
在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);
}
}
分类通过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调用attachCategories方法,实现将方法列表、属性和协议从类别附加到类。所有的分类是按照顺序进行加载的。attachCategories中详细的处理步骤就不做过多分析。
加载时机
attachCategories方法中是实现了分类的加载的处理的,那么分类是在什么时机加载的我们可以通过attachCategories的调用来简单的分析下。
首先我们找到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));
}
通过打印以及调试发现分类的加载的调用顺序为load_images->loadAllCategories->load_categories_nolock->attachCategories。