Category和Extension
- extension可以看做是category的一个特例,有时候又叫匿名分类,可以为类添加一些私有的成员变量和成员函数
- category可以在不改变原有类的基础上为类扩展方法,category不能添加成员变量,但可以通过实现其getter和setter方法的方式来添加,(通过OBJC_ASSOCIATE方式添加)
- extension通常写在.m文件中,定义的变量和方法都是私有的,也可以写在.h中,变量是共有变量
- extension中的方法一定要实现,category中的没有限制
# Extension
extension看起来像一个匿名的category,但实际上几乎是完全不同的东西。extension是在编译期决定的,就是类的一部分,和Interface和Implement一起形成一个完整的类。extension一般用来隐藏类的私有信息,必须有源码才能添加extension,所以无法通过extension为系统的类添加extension。
而category是在运行时决定的。这也就是为什么extension可以添加实例变量,而category不可以(因为在运行时,对象的内存布局已经确定,添加实例变量就会破坏类的内存布局)
# Category
category的主要作用是为已经存在的类添加方法
You use categories to define additional methods of an existing class—even one whose source code is unavailable to you—without subclassing. You typically use a category to add methods to an existing class, such as one defined in the Cocoa frameworks. The added methods are inherited by subclasses and are indistinguishable at runtime from the original methods of the class. You can also use categories of your own classes to:
- Distribute the implementation of your own classes into separate source files—for example, you could group the methods of a large class into several categories and put each category in a different file.
- Declare private methods.
You add methods to a class by declaring them in an interface file under a category name and defining them in an implementation file under the same name. The category name indicates that the methods are an extension to a class declared elsewhere, not a new class.
官方推荐Category的两个使用场景:
- 可以把类的实现分开在不同的文件里面
- 声明私有方法
# Category结构分析
试着写一个Category,并用clang进行-rewrite-objc。首先在runtime源码中可以找到Category的结构体:category_t
:
struct category_t {
const char *name; // 类名
classref_t cls; // 类
struct method_list_t *instanceMethods; // Category中给类添加的的实例方法列表
struct method_list_t *classMethods; // category中给类添加的类方法类别
struct protocol_list_t *protocols; // category中实现的协议列表
struct property_list_t *instanceProperties; // category中添加的属性
// 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;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
从Category的结构体可以看出,可以添加实例方法、类方法、实现协议、添加属性,而不能够添加实例变量。
写一个Category:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface YKDemoClass : NSObject
- (void)printName;
@end
@interface YKDemoClass (Addtion)<NSCopying>
@property (nonatomic, copy) NSString *addName;
- (void)printName;
- (void)printAddName;
+ (void)printClasName;
@end
@interface YKDemoClass (MyAddtion)
- (void)printMyAddtionName;
@end
NS_ASSUME_NONNULL_END
#import "YKDemoClass.h"
@implementation YKDemoClass
- (void)printName {
NSLog(@"%@", @"DemoClass");
}
@end
@implementation YKDemoClass (Addtion)
- (void)printName {
NSLog(@"%@", @"AddtionDemoClass");
}
- (void)printAddName {
NSLog(@"%@", @"Addtion");
}
+ (void)printClasName {
NSLog(@"%@", @"YKDemoClass+Addtion");
}
- (id)copyWithZone:(nullable NSZone *)zone {
NSLog(@"%@", @"copyWithZone");
return self;
}
@end
@implementation YKDemoClass (MyAddtion)
- (void)printMyAddtionName {
NSLog(@"%@", @"MyAddtion");
}
@end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
使用clang命令进行处理得到cpp clang -rewrite-objc YKDemoClass.m
,在文件最后找到如下代码:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_YKDemoClass_$_Addtion __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_YKDemoClass_Addtion_printName},
{(struct objc_selector *)"printAddName", "v16@0:8", (void *)_I_YKDemoClass_Addtion_printAddName},
{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_YKDemoClass_Addtion_copyWithZone_}}
};
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_YKDemoClass_$_Addtion __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printClasName", "v16@0:8", (void *)_C_YKDemoClass_Addtion_printClasName}}
};
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_$_YKDemoClass_$_Addtion __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
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_YKDemoClass_$_Addtion __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"addName","T@\"NSString\",C,N"}}
};
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_YKDemoClass;
static struct _category_t _OBJC_$_CATEGORY_YKDemoClass_$_Addtion __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"YKDemoClass",
0, // &OBJC_CLASS_$_YKDemoClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_YKDemoClass_$_Addtion,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_YKDemoClass_$_Addtion,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_YKDemoClass_$_Addtion,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_YKDemoClass_$_Addtion,
};
static void OBJC_CATEGORY_SETUP_$_YKDemoClass_$_Addtion(void ) {
_OBJC_$_CATEGORY_YKDemoClass_$_Addtion.cls = &OBJC_CLASS_$_YKDemoClass;
}
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_YKDemoClass_$_MyAddtion __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printMyAddtionName", "v16@0:8", (void *)_I_YKDemoClass_MyAddtion_printMyAddtionName}}
};
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_YKDemoClass;
static struct _category_t _OBJC_$_CATEGORY_YKDemoClass_$_MyAddtion __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"YKDemoClass",
0, // &OBJC_CLASS_$_YKDemoClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_YKDemoClass_$_MyAddtion,
0,
0,
0,
};
static void OBJC_CATEGORY_SETUP_$_YKDemoClass_$_MyAddtion(void ) {
_OBJC_$_CATEGORY_YKDemoClass_$_MyAddtion.cls = &OBJC_CLASS_$_YKDemoClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_YKDemoClass_$_Addtion,
(void *)&OBJC_CATEGORY_SETUP_$_YKDemoClass_$_MyAddtion,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS_$_YKDemoClass,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_YKDemoClass_$_Addtion,
&_OBJC_$_CATEGORY_YKDemoClass_$_MyAddtion,
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
从代码中可以看到:
生成了以下结构 :
实例方法列表:
_OBJC_$_CATEGORY_INSTANCE_METHODS_YKDemoClass_$_Addtion
类方法列表:
_OBJC_$_CATEGORY_CLASS_METHODS_YKDemoClass_$_Addtion
实现的协议列表:
_OBJC_CATEGORY_PROTOCOLS_$_YKDemoClass_$_Addtion
属性列表:
_OBJC_$_PROP_LIST_YKDemoClass_$_Addtion
而且有static修饰,所以在同一个编译单元中,Category名称不能重复
生成了名字为
_OBJC_$_CATEGORY_YKDemoClass_$_Addtion
的_category_t
结构体,并用1中的实例方法列表、类方法列表、协议列表和属性列表进行初始化在DATA段
__objc_catlist
里保存了大小为2的_category_t
的数组L_OBJC_LABEL_CATEGORY_$
(我们添加了两个Category)
# Category加载过程
要想弄清楚Category的加载过程,可以先了解下App启动过程,App启动时先加载dyld(动态库加载器),执行_dyld_start
,然后加载动态库(Image),ImageLoader对Image进行初始化,调用libSystem_initializer
,之后会调用runtime里的_objc_init
方法,注册image的三个回调函数map、init和unmap。在XCode中Debug一下就可以发现,dyld的源码就不贴了,回头专门写一篇文章
而_dyld_objc_notify_register
可以在在objc-os.mm中可以找到OC运行的入口方法:
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
//_dyld_objc_notify_register注册三个回调函数,
// map_images:dyld将image加载到内存时调用
// load_images:dyld初始化image,load方法都在此时调用
// unmap_image:将image移除内存时调用
// map_images这个方法,用来处理被dyld映射过来的镜像,注册后就会被调用
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
刚开始以为category被附加到类中是在函数map_images
执行过中,其实不然。
首先我们看map_images
方法,dyld会调用map_images
函数:
if ( objcImageCount != 0 ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
uint64_t t0 = mach_absolute_time();
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
uint64_t t1 = mach_absolute_time();
ImageLoader::fgTotalObjCSetupTime += (t1-t0);
}
2
3
4
5
6
7
其中(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
就是调用map_images
函数,我们回到runtime中看map_images
执行过程,而在objc-runtime-new.h中显示其实是调用了map_images_nolock
,这个方法代码很多,截取关键代码如下:
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
2
3
发现其实是调用_read_images
方法,这个方法更长,包含了很多功能:发现Class、Protocol,Category等等
而其中一部分就是发现Category的,如下:
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/53119145
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
2
3
4
5
6
7
8
9
从注释可以看到:只有在分类初始化并将数据加载类后才执行,对于运行时出现的分类,将分类的发现推迟到_dyld_objc_notify_register
方法完成后的第一次load_images
调用。
因为didInitialAttachCategories
的默认值为false,而 Attach categories是在后面调用realizeClassWithoutSwift
时的最后一步做的,我们接着看load_images
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
首先didInitialAttachCategories
的默认值为false,didCallDyldNotifyRegister
的值是在_dyld_objc_notify_register
结束后设置为true的,也印证了上面_read_images
中的解释。
而loadAllCategories
方法会循环调用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(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
这里做的事情就是读取category_t数组,分别将实例方法和类方法添加到cls上和cls的ISA上,分别调用attachCategories
方法:
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
这里才是真正添加方法、属性和协议的地方,通过列表的attachLists
方法进行添加,此方法就比较简单了:
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;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<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;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
注意:
- 从整个加载过程来看,category的方法没有替换掉原来已经存在的方法,比如category和原来的类都有print这个方法,那么category附加完成后,类的方法列表中会有两个print方法
- category中的方法在新的方法列表中排在了前面,而原来类中的方法被放到了新方法列表的后面。也就是说越晚加载的category,其方法在方法列表中排列越靠前,而在方法调用的时候则是顺着方法列表查找,这也就是我们所说的category的方法会“覆盖”掉原来类中同名方法的原因。
# Category和+load方法
在类中和category中都可以有+load方法,那有两个问题:
在类的+load方法调用的时候,可以调用category中的方法吗?
可以调用,从源码来看,附加category到类的工作是先于+load方法执行的
这些+load方法的调用顺序是什么样的?
/*********************************************************************** * call_load_methods * Call all pending class and category +load methods. * Class +load methods are called superclass-first. * Category +load methods are not called until after the parent class's +load. * * This method must be RE-ENTRANT, because a +load could trigger * more image mapping. In addition, the superclass-first ordering * must be preserved in the face of re-entrant calls. Therefore, * only the OUTERMOST call of this function will do anything, and * that call will handle all loadable classes, even those generated * while it was running. * * The sequence below preserves +load ordering in the face of * image loading during a +load, and make sure that no * +load method is forgotten because it was added during * a +load call. * Sequence: * 1. Repeatedly call class +loads until there aren't any more * 2. Call category +loads ONCE. * 3. Run more +loads if: * (a) there are more classes to load, OR * (b) there are some potential category +loads that have * still never been attempted. * Category +loads are only run once to ensure "parent class first" * ordering, even if a category +load triggers a new loadable class * and a new loadable category attached to that class. * * Locking: loadMethodLock must be held by the caller * All other locks must not be held. **********************************************************************/ 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; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
从源码来看,先执行类的+load方法,再执行category的+load,而category的+load方法顺序是根据编译顺序而定的。
# Category和方法覆盖
在category加载过程中已经讲了产生方法覆盖的原因,那我们如何调用到被覆盖的方法呢?
其实类中的方法并不是被替换,只是category在方法列表的前面而已,所以可以顺着方法列表往后找到最后一个,就是原来类中的方法:
void callOriginPrint() {
Class cls = [YKDemoClass class];
YKDemoClass *demo = [[YKDemoClass alloc] init];
unsigned int methodCount;
Method *methodList = class_copyMethodList(cls, &methodCount);
IMP lastImp = NULL;
SEL lastSel = NULL;
for (NSInteger i = 0; i < methodCount; i++) {
Method method = methodList[i];
NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) encoding:NSUTF8StringEncoding];
if ([@"printName" isEqualToString:methodName]) {
lastImp = method_getImplementation(method);
lastSel = method_getName(method);
}
}
typedef void (*PrintMethod)(id, SEL);
if (lastImp != NULL) {
PrintMethod printMethod = (PrintMethod)lastImp;
printMethod(demo, lastSel);
}
free(methodList);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Category和关联对象
category里面无法添加实例变量,但是我们很多时候需要在category中添加和对象关联的值,这个时候就需要用到关联对象来实现了。
- (void)setAddName:(NSString *)addName {
objc_setAssociatedObject(self, @selector(addName), addName, OBJC_ASSOCIATION_COPY);
}
- (NSString *)addName {
return objc_getAssociatedObject(self, @selector(addName));
}
2
3
4
5
6
7
再次查看runtime源码,objc_setAssociatedObject
调用objc-references.mm中的_object_set_associative_reference
方法:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
可以看到关联对象都是由AssociationsManager
管理,而AssociationsManager
定义如下:
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
其实就是管理了一个static的哈希表,以object对象指针为key,value为ObjectAssociationMap
,map保存着的key和ObjcAssociation
对象,而ObjcAssociation
就是value和objc_AssociationPolicy
。
对象销毁逻辑,代码如下:
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
runtime销毁对象函数时会判断是否有关联对象,如果有,调用_object_remove_assocations
移除关联对象