写一个 Person
的分类:Person+DO
Person+DO.h
文件:
#import "Person.h"
@interface Person (DO)
@property (nonatomic, assign) int number;
- (void)testInstanceMethod;
+ (void)testClassMethod;
@end
Person_DO.m
文件:
#import "Person+DO.h"
@implementation Person (DO)
- (void)testInstanceMethod{}
+ (void)testClassMethod{}
@end
在终端输入以下命令:
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+DO.m -o Person+DO-arm64.cpp
打开 Person+DO-arm64.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 /*_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_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"testInstanceMethod", "v16@0:8", (void *)_I_Person_DO_testInstanceMethod}}
};
_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DO
:从名字(INSTANCE_METHODS)可看出是对象方法列表结构体
testInstanceMethod
:正是我们分类中定义的对象方法
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_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"testClassMethod", "v16@0:8", (void *)_C_Person_DO_testClassMethod}}
};
_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_DO
:从名字(CLASS_METHODS)可看出是类方法列表结构体
testClassMethod
:正是我们分类中定义的类方法
static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCopying [] __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"@24@0:8^{_NSZone=}16"
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
};
struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
0,
"NSCopying",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
可看出,协议方法先通过 _method_list_t
结构体存储,之后通过 _protocol_t
结构体存储在 _OBJC_CATEGORY_PROTOCOLS_$_Person_$_DO
中和 _protocol_list_t
结构体一一对应。
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_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"number","Ti,N"}}
};
从属性列表结构体中,发现了我们自己写的 number
属性
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;
static struct _category_t _OBJC_$_CATEGORY_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DO,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_DO,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_DO,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_DO,
};
static void OBJC_CATEGORY_SETUP_$_Person_$_DO(void ) {
_OBJC_$_CATEGORY_Person_$_DO.cls = &OBJC_CLASS_$_Person;
}
struct _class_t OBJC_CLASS_$_Person
:_class_t
类型的 OBJC_CLASS_$_Person
结构体
_OBJC_$_CATEGORY_Person_$_DO.cls = &OBJC_CLASS_$_Person;
:cls
指针指向分类的主类类对象 Person
的地址
至此,通过以上分类的源码,我们在分类中定义的 对象方法
、 类方法
、 属性
、协议
等都存放在 catagory_t
结构体中。
总结:
- Category的实现原理:将分类中的方法,属性,协议数据放在
category_t
结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。 - Category中不能加属性:Category可以添加属性,但是并不会自动生成成员变量及
set/get
方法。因为category_t
结构体中并不存在成员变量。
进一步分析:
成员变量是存放在实例对象中的,并且编译使就已经决定好了。而分类是在运行时才去加载的,无法在程序运行时将分类的成员变量添加到实例对象的结构体中。因此分类中不可以添加成员变量。
load 和 initialize
objc-loadmethod.mm
文件中:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
优先调用类的 load
方法,之后调用分类的 load
方法
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
initialize
特点:
- 第一次使用类的时候会调用(第一次接收到消息)
- 调用子类的
initialize
之前,会先保证调用父类的initialize
方法 - 如果之前已经调用过
initialize
,就不会再调用initialize
方法 - 当分类重写
initialize
方法时会先调用分类的方法
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
load
方法通过直接拿到 load
方法地址进行调用
总结:
load
和 initialize
区别:
-
load
是根据函数地址直接调用
initialize
是通过objc_msgSend
调用 -
load
是runtime
加载类、分类的时候调用(只会调用1次)
initialize
是类第一次接收到消息的时候调用,每一个类只会initialize
一次(父类的initialize
方法可能会被调用多次,子类未实现) - 分类中
load
方法不会覆盖本类的load
方法
如果分类实现了initialize
方法,就覆盖类本身的initialize
方法