深入分析Runtime

    Objective-C作为一门动态语言,其核心就是Runtime。本篇将从源码着手,分析Runtime的几个关键地方,使得我们可以更好的理解Runtime的运行机制。我们所有的类都继承于NSObject,所以我们先把结构列出来

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
// 每个OC对象都有一个isa成员
@interface NSObject <NSObject> {
Class isa ;
}

// isa又是一个obc_class的结构体
typedef struct objc_class *Class;

struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;

class_rw_t *data() {
return bits.data();
}
......
};

// objc_class又继承objc_object
struct objc_object {
private:
isa_t isa;
......
};

struct class_rw_t {
uint32_t flags;
uint32_t version;

const class_ro_t *ro;

method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
......
};

struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif

const uint8_t * ivarLayout;

const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;

const uint8_t * weakIvarLayout;
property_list_t *baseProperties;

method_list_t *baseMethods() const {
return baseMethodList;
}
};

    所以NSObject的结构可以大致看成下面这样的:

1
2
3
4
5
6
7
8
9
10
11
@interface NSObject <NSObject> {
isa_t isa;
Class superclass;
cache_t cache;
class_data_bits_t bits;

class_rw_t *data() {
return bits.data();
}
....
}

0x01 isa

  在ARM64之前,isa是直接指向类对象或元类对象的地址,但是在ARM64的时候,isa包含了更多的信息。isa_t是一个共用体(union),结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
union isa_t 
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits; // unsigned long

# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};

  作为共用体,所有成员共享一块区域。uintptr_t表示的是unsigned long,大小是8个字节,其作为共用体里最大的成员,所以这个共用体也占据8个字节的大小,可能这里有些人会觉得疑惑,明明struct里有9个uintptr_t成员,怎么会共用体才8个字节。要解释这个问题,我们需要知道位域这个概念。

  位域,是指信息在存储时,并不需要占用一个完整的字节,我们可以指定其大小。这样做的目的可以使得我们可以节省空间。举个例子,假设我们养一个宠物,给它设定三个属性,分别为吃饭了没,喝水了没,洗澡了没。

1
2
3
@property (nonatomic, assign) BOOL eat;
@property (nonatomic, assign) BOOL drink;
@property (nonatomic, assign) BOOL sleep;

  我们知道,BOOL占用一个字节,三个属性占用了三个字节。对于这三个属性的值非真即假,用三个字节我觉得很浪费,那么如何使用位域更节省的使用空间呢?对于一个属性非真即假,我们用二进制就可以来满足,1个字节又有8位,所以三个属性的结果只需要3位即可满足。比如睡觉在最后一位,喝水倒数第二位,睡觉倒数第三位,假如满足吃饭的条件,值就是0b0000 0001。如果满足吃饭也满足喝水,值就是0b0000 0011,以此类推。

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@interface Animal : NSObject

- (void)setEat:(BOOL)eat;
- (void)setDrink:(BOOL)drink;
- (void)setSleep:(BOOL)sleep;

-(BOOL)eat;
-(BOOL)drink;
-(BOOL)sleep;

@end

@implementation Animal {
union {
char bits; // 共用体占据1个字节

struct {
bool eat : 1; // 指定占据1位
bool drink : 1;
bool sleep : 1;
};
}_action;
}


- (void)setEat:(BOOL)eat {
if (eat) {
_action.bits |= (1 << 0); // 1 << 0即0000 0001;或上1将对应位设置为1
}else{
_action.bits &= ~(1 << 0); // ~(1 << 0)即1111 1110;与上,将对应为设置为0
}
}

- (void)setDrink:(BOOL)drink {
if (drink) {
_action.bits |= (1 << 1); // 1 << 1,左移1位,即0000 0010;或上1将对应位设置为1
}else{
_action.bits &= ~(1 << 1); // ~(1 << 1)即1111 1101;与上,将对应为设置为0
}
}

- (void)setSleep:(BOOL)sleep {
if (sleep) {
_action.bits |= (1 << 2); // 1 << 2即0000 0100;或上1将对应位设置为1
}else{
_action.bits &= ~(1 << 2); // ~(1 << 2)即1111 1011;与上,将对应为设置为0
}
}

- (BOOL)eat {
return !!(_action.bits & (1 << 0)); // 与上,来进行取值
}

- (BOOL)drink {
return !!(_action.bits & (1 << 1));
}

- (BOOL)sleep {
return !!(_action.bits & (1 << 2));
}

@end

// 测试代码
int main(int argc, char * argv[]) {
Animal *a = [Animal new];
NSLog(@"%@", [NSString stringWithFormat:@"宠物%@,%@,%@", [a eat] ? @"有吃饭":@"没有吃饭", [a drink] ? @"有喝水":@"没有喝水", [a sleep] ? @"有睡觉":@"没有睡觉"]);
[a setEat:YES];
[a setDrink:NO];
[a setSleep:YES];
NSLog(@"%@", [NSString stringWithFormat:@"宠物%@,%@,%@", [a eat] ? @"有吃饭":@"没有吃饭", [a drink] ? @"有喝水":@"没有喝水", [a sleep] ? @"有睡觉":@"没有睡觉"]);
}

// 运行结果:
2018-05-21 10:03:24.118022+0800 testData[67088:104712921] 宠物没有吃饭,没有喝水,没有睡觉
2018-05-21 10:03:24.119529+0800 testData[67088:104712921] 宠物有吃饭,没有喝水,有睡觉

  回到isa_t共用体,我们详细解释下里面每一位的意思:

  • nonpointer,占据1位

    0 : 直接执行类地址或者元类地址

    1:包含更多信息

  • has_assoc,占据1位

    0:没有关联对象

    1:有关联对象

  • has_cxx_dtor,占据1位

    0:没有C++类的析构函数

    1:有C++类的析构函数

  • shiftcls, 占据33位

    类地址或元类地址,具体值的算法= isa的地址 & 0x0000000ffffffff8

  • magic,占据1位

    0:对象没有完成初始化

    1:对象完成初始化

  • weakly_referenced,占位1位

    0:没有被弱引用指向

    1:有被弱引用指向

  • deallocating,占据1位

    0:没有正在被释放

    1:正在被释放

  • has_sidetable_rc,占据1位

    0:引用计数器可以被保存在isa里

    1:引用计数器太大,不能保存在isa里

  • extra_rc,占据19位

    存储引用计数 - 1的值

  首先,证明在ARM64上isa已经不是直接指向类对象或元类对象,测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char * argv[]) {
Animal *a = [Animal new];
Class cls = object_getClass(a);
}

// 打印a->isa的值
(lldb) p/x a->isa
(Class) $1 = 0x000001a1025496d5
// 打印cls的值
(lldb) p/x cls
(Class) $0 = 0x00000001025496d0

  我们发现的确isa的地址跟类地址的值是不一致的,要通过isa拿到真实的类地址需要与上0x0000000ffffffff8

1
2
p/x 0x000001a1025496d5 & 0x0000000ffffffff8
(long) $2 = 0x00000001025496d0 // 即cls的地址值

  将0x0000000ffffffff8转换成二进制值

1
2
p/t 0x0000000ffffffff8
(long) $3 = 0b0000000000000000000000000000111111111111111111111111111111111000

  与上面说的shiftcls一致,即倒数第四位开始的33位。我们知道如果要取值,就是与1进行取与操作

1
2
3
0x000001a1025496d  = 0b0000000000000000000000011010000100000010010101001001011011010101
0x0000000ffffffff8 = 0b0000000000000000000000000000111111111111111111111111111111111000
& = 0b0000000000000000000000000000000100000010010101001001011011010000

  接着,我们设置一下关联对象,证明has_assoc是否会变为1

1
2
3
4
5
6
7
设置前:0b0000000000000000000000011010000100000010010101001001011011010101  // 倒数第二位为0

// 测试代码
Animal *a = [Animal new];
objc_setAssociatedObject(a, @selector(age), @100, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

设置后:0b0000000000000000000000011010000100000000010010011001011011101111 // 倒数第二位为1

  其他位就不一一做实验了。

0x02 方法查找流程

isa和superClass关系示意图

  在之前的Category文章里,其实已经提到这个,所有的调用流程如上图所示。我们只需要记住,实例对象只保存成员变量的值,实例对象的方法(比如- (void)eat;)保存在其类对象的方法表里,以及协议信息和成员变量信息(名字,大小等)也是保存在类对象对应的协议表和成员变量表中。而类方法(比如+ (void)eat),则保存在元类(meta-class)的的方法表中。

  所以下文的表述中,如果说的是类的方法表,说明找的是实例方法;如果说的是元类的方法表,说明找的是类方法。

0x03 objc_msgSend

  objc_msgSend是通过汇编代码实现的,Objective-C中调用方法实际都是走的objc_msgSend函数,所以这个函数是会被十分频繁的调用,以汇编实现将提升效率。

  我们首先写下测试代码:

1
2
Animal *a = [Animal new];
[a eat];

  符号断点在eat方法上,看下objc_msgSend汇编代码,所有方法调用都会带两个参数,一个是self,即调用者,第二个是_cmd,即调用方法,分别保存在x0和x1寄存器里。

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
61
62
63
64
65
66
67
68
69
70
71
72
libobjc.A.dylib`objc_msgSend:
; x0即调用者Animal对象,#0x0即nil,也就是Animal对象是不是为0
-> 0x183c5c900 <+0>: cmp x0, #0x0
; 如果是nil,就跳转到0x183c5c96c地址处继续执行
0x183c5c904 <+4>: b.le 0x183c5c96c
; 相当于a->isa的地址保存到x13寄存器。x0表示Animal实例对象a的地址,[x0]表示a->isa的地址
0x183c5c908 <+8>: ldr x13, [x0]
; 这个上面提过,isa地址 与上 #0xffffffff8得到类对象地址,并将类对象的地址保存到x16寄存器
0x183c5c90c <+12>: and x16, x13, #0xffffffff8
; 在类对象地址偏移16个字节的位置,读取16个字节,分别放进x10寄存器和x11寄存器,详细看下面注解①
0x183c5c910 <+16>: ldp x10, x11, [x16, #0x10]
; w11保存的是cache_t结构体的mask成员,也就是将_cmd的低32位与mask做与运算。目的是取出方法的在散列表中的位置,保存到x12寄存器的低32位
0x183c5c914 <+20>: and w12, w1, w11
; x10保存着散列表的首地址,x12保存着方法的位置(假设为index),lsl #4表示左移4位,相当于乘以16,而且散列表每个成员(bucket_t)也正好占用16个字节,所以方法的实际地址 = 首地址 + index * 16。结果保存到x12寄存器
0x183c5c918 <+24>: add x12, x10, x12, lsl #4
;取出x12地址开始的16个字节,前8个字节保存到x9寄存器,后8个字节保存到x17寄存器。我们知道x12保存的是我们所调用的方法在散列表中的地址,其结构是bucket_t结构体,所以方法名key保存在x9寄存器,方法地址imp保存在x17寄存器。
0x183c5c91c <+28>: ldp x9, x17, [x12]
; 比较_cmd名字和散列表中所找到的方法的名字是否一致
0x183c5c920 <+32>: cmp x9, x1
; 如果不一致,就跳转到0x183c5c92c地址
0x183c5c924 <+36>: b.ne 0x183c5c92c ; <+44>
; 如果名字一致,就直接跳转到方法的实现地址。
0x183c5c928 <+40>: br x17
; 之前如果方法和找到的缓存方法的名字不一样,就来到了这里,如果x9是0,那就执行_objc_msgSend_uncached函数
0x183c5c92c <+44>: cbz x9, 0x183c5cc00 ; _objc_msgSend_uncached
; x10保存是散列表首地址,x12保存的是当前back_t成员的地址
0x183c5c930 <+48>: cmp x12, x10
; 如果x12跟x10值一样,说明散列表已经遍历完了, 跳转到0x183c5c940
0x183c5c934 <+52>: b.eq 0x183c5c940 ; <+64>
; 如果x12跟x10值不一致,说明没遍历结束,[x12, #-0x10]!往前移动16个字节,即来到前一个bucket_t成员。
0x183c5c938 <+56>: ldp x9, x17, [x12, #-0x10]!
; 跳转到上面的cmp x9, x1,继续比较方法名字是否一致
0x183c5c93c <+60>: b 0x183c5c920 ; <+32>
; 散列表遍历完成后,会来到这里。w11是掩码mask,将掩码左移4位,即左移16个字节,我们知道掩码保存的是散列表的长度减1。x12此时是散列表首地址,两者相加再次保存进x12寄存器,也就是x12保存的就是散列表最后一个成员的地址。
0x183c5c940 <+64>: add x12, x12, w11, uxtw #4
; 同<+28>
0x183c5c944 <+68>: ldp x9, x17, [x12]
; 比较名字
0x183c5c948 <+72>: cmp x9, x1
; 不一致就执行_objc_msgSend_uncached
0x183c5c94c <+76>: b.ne 0x183c5c954 ; <+84>
; 一致就调用方法,接下来就跟前面差不多了,后面不做解释
0x183c5c950 <+80>: br x17
0x183c5c954 <+84>: cbz x9, 0x183c5cc00 ; _objc_msgSend_uncached
0x183c5c958 <+88>: cmp x12, x10
; 如果又回到了表头,则跳转到0x183c5c968处
0x183c5c95c <+92>: b.eq 0x183c5c968 ; <+104>
0x183c5c960 <+96>: ldp x9, x17, [x12, #-0x10]!
0x183c5c964 <+100>: b 0x183c5c948 ; <+72>
; 即执行_objc_msgSend_uncached函数
0x183c5c968 <+104>: b 0x183c5cc00 ; _objc_msgSend_uncached
0x183c5c96c <+108>: b.eq 0x183c5c9a4 ; <+164>
0x183c5c970 <+112>: mov x10, #-0x1000000000000000
0x183c5c974 <+116>: cmp x0, x10
0x183c5c978 <+120>: b.hs 0x183c5c990 ; <+144>
0x183c5c97c <+124>: adrp x10, 209389
0x183c5c980 <+128>: add x10, x10, #0x270 ; =0x270
0x183c5c984 <+132>: lsr x11, x0, #60
0x183c5c988 <+136>: ldr x16, [x10, x11, lsl #3]
0x183c5c98c <+140>: b 0x183c5c910 ; <+16>
0x183c5c990 <+144>: adrp x10, 209389
0x183c5c994 <+148>: add x10, x10, #0x2f0 ; =0x2f0
0x183c5c998 <+152>: ubfx x11, x0, #52, #8
0x183c5c99c <+156>: ldr x16, [x10, x11, lsl #3]
0x183c5c9a0 <+160>: b 0x183c5c910 ; <+16>
0x183c5c9a4 <+164>: mov x1, #0x0
0x183c5c9a8 <+168>: movi d0, #0000000000000000
0x183c5c9ac <+172>: movi d1, #0000000000000000
0x183c5c9b0 <+176>: movi d2, #0000000000000000
0x183c5c9b4 <+180>: movi d3, #0000000000000000
0x183c5c9b8 <+184>: ret
0x183c5c9bc <+188>: nop

  注解① 回到NSObject结构,我们可以发现偏移16个字节就是cache_t成员的位置。

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
@interface NSObject <NSObject> {
isa_t isa; // 8个字节
Class superclass; // class其实也就是isa_t,也是8个字节
cache_t cache;
class_data_bits_t bits;

class_rw_t *data() {
return bits.data();
}
......
}

// cache_t结构体
struct cache_t {
struct bucket_t *_buckets; // 指针8个字节,指向一个散列表
mask_t _mask; // 掩码。 mask_t 即uint32_t,是4个字节。表示的是散列表长度 - 1,类似于数组,数组长度为n,但是取值从0开始的,所以需要n-1。
mask_t _occupied; // 4个字节。表示的缓存方法的个数
......
};

// bucket_t结构体
struct bucket_t {
cache_key_t _key; // 方法名,cache_key_t即unsigned long,8个字节
IMP _imp; // 方法的实现地址,指针8个字节
......
}; // bucket_t占用16个字节

  所以,我们可以发现cache成员位置开始取16个字节,其实就是把散列表bucket_t的首地址保存到x10寄存器;我们知道一个寄存器占用8个字节,所以mask掩码保存到x11寄存器的低32位,occupied保存到x11寄存器的高32位。

  上面的流程总结如下:

  • 判断对象是否为空
  • 取到isa地址
  • 根据isa地址拿到类对象的地址
  • 来到缓存成员cache_t的地址,为的是开始遍历缓存散列表
  • 计算出我们调用方法在散列表的位置index
  • 根据散列表首地址和散列表成员长度计算出方法在散列表的具体地址:方法地址 = 首地址 + 成员长度 * index
  • 比较我们调用的方法名字和散列表中找到的方法的名字是否一致,如果方法名字为空(说明肯定没被缓存过),那么就跳转到_objc_msgSend_uncached函数,一般第一次调用方法都会执行到这里的时候就跳转走了。
  • 如果调用的方法有名字(说明被缓存过了),且一致的话就直接调用函数执行地址。
  • 不一致的话,往前找前一个成员,也是进行名字比较。依次类推,直到来到首地址。
  • 如果来到首地址,这时候还是没找到跟我们调用方法名字一致的散列表成员,就扩大搜索范围,来到散列表尾部再往前一个个遍历,查找与调用方法名字一致的散列表成员。
  • 第二次来到表头还是没找到,就调用_objc_msgSend_uncached函数。

  前面说到,如果缓存里找不到缓存,就会调用_objc_msgSend_uncached,我们看下源码

1
2
3
4
5
6
7
8
9
10
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search

MethodTableLookup
br x17

END_ENTRY __objc_msgSend_uncached

  源码里有一个MethodTableLookup的宏

1
2
3
4
5
.macro MethodTableLookup
......
bl __class_lookupMethodAndLoadCache3
......
.endmacro

  里面调用c函数__class_lookupMethodAndLoadCache3

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
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
// 因为缓存里没找到,所以入参cache为NO
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

// 查找主要在这个函数里面
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;

runtimeLock.assertUnlocked();

// 如果cache为YES,显然这里是NO
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}

// 加锁
runtimeLock.read();

// 如果类还没被构造好,即class_rw_t结构体还没被初始化
if (!cls->isRealized()) {
runtimeLock.unlockRead();
runtimeLock.write();

realizeClass(cls);

runtimeLock.unlockWrite();
runtimeLock.read();
}

// 如果类还是没初始化,一般第一次来到某个函数,就会执行一次也是唯一一次的initialize方法。
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}


retry:
runtimeLock.assertReading();


// 再次尝试在本类缓存中查找,如果找到了就结束本函数
imp = cache_getImp(cls, sel);
if (imp) goto done;

// 还是没在缓存中找到,就来到类的方法表中进行查找,如果找到了,在调用者缓存中保存,然后结束本函数
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}

// 尝试到父类的缓存表中查找,如果父类缓存表中没有找到,就去父类的方法表查找
// 这是一个for循环,所以会遍历直到基类,即NSObject结束
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}

//   cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}

// 方法表中如果找到了,在缓存中保存,然后结束本函数
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}


// 都没有找到,进入动态方法解析流程:首先看看是否元类,是元类调用resolveClassMethod方法和resolveInstanceMethod方法,如果不是元类调用resolveInstanceMethod方法
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
triedResolver = YES;
goto retry;
}


// 如果是resolveClassMethod方法或resolveInstanceMethod方法也没被调用,直接调用转发机制。
// 即先调用forwardingTargetForSelector,如果这个方法返回nil,继续调用methodSignatureForSelector,如果返回不为空继续调用forwardInvocation;如果还是为空,调用doesNotRecognizeSelector,则闪退报错
// _objc_msgForward_impcache会在下文做解释
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

done:
runtimeLock.unlockRead();

return imp;
}


void _class_resolveMethod(Class cls, SEL sel, id inst)
{
// 判断不是元类,调用resolveInstanceMethod方法
if (! cls->isMetaClass()) {
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// 如果是元类,首先调用resolveClassMethod方法,再次从类或者方法列表中查找,如果还是没有,再次调用resolveInstanceMethod方法
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}

  _objc_msgForward_impcache又是一个汇编函数

1
b      0x183c5cda0               ; _objc_msgForward

  内部就是调用_objc_msgForward函数,里面就是跳转到CoreFoundation里的CF_forwarding_prep_0

1
2
3
4
5
6
7
    0x183c5cda0 <+0>:  adrp   x17, 209389
0x183c5cda4 <+4>: ldr x17, [x17, #0x168]
-> 0x183c5cda8 <+8>: br x17

; 读取x17寄存器的值,即_CF_forwarding_prep_0函数
(lldb) register read x17
x17 = 0x000000018497a3c0 CoreFoundation`_CF_forwarding_prep_0

  _CF_forwarding_prep_0继续会调用___forwarding___函数,

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
CoreFoundation`_CF_forwarding_prep_0:
; 保存fp和lr寄存器
-> 0x18497a3c0 <+0>: stp x29, x30, [sp, #-0x10]!
0x18497a3c4 <+4>: mov x29, sp
0x18497a3c8 <+8>: sub sp, sp, #0xd0 ; =0xd0
0x18497a3cc <+12>: str q7, [sp, #0xc0]
0x18497a3d0 <+16>: str q6, [sp, #0xb0]
0x18497a3d4 <+20>: str q5, [sp, #0xa0]
0x18497a3d8 <+24>: str q4, [sp, #0x90]
0x18497a3dc <+28>: str q3, [sp, #0x80]
0x18497a3e0 <+32>: str q2, [sp, #0x70]
0x18497a3e4 <+36>: str q1, [sp, #0x60]
0x18497a3e8 <+40>: str q0, [sp, #0x50]
0x18497a3ec <+44>: str x8, [sp, #0x40]
0x18497a3f0 <+48>: str x7, [sp, #0x38]
0x18497a3f4 <+52>: str x6, [sp, #0x30]
0x18497a3f8 <+56>: str x5, [sp, #0x28]
0x18497a3fc <+60>: str x4, [sp, #0x20]
0x18497a400 <+64>: str x3, [sp, #0x18]
0x18497a404 <+68>: str x2, [sp, #0x10]
; sp偏移8位开始的8位,存放方法,即_cmd
0x18497a408 <+72>: str x1, [sp, #0x8]
; sp开始的8位保存对象,即self
0x18497a40c <+76>: str x0, [sp]
; sp保存到x0
0x18497a410 <+80>: mov x0, sp
0x18497a414 <+84>: mov x1, #0x0
; 跳转到___forwarding___函数
0x18497a418 <+88>: bl 0x184a94064 ; ___forwarding___

  里面进行转发流程,如果转发流程中的方法都没被实现,那么调用调用doesNotRecognizeSelector方法,程序就此闪退。汇编还是比较好理解,所以直接阅读一部分汇编代码,如果有兴趣阅读全部的汇编代码,只需要自己下符号断点,自己观察即可:

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
CoreFoundation`___forwarding___:
0x184a94064 <+0>: stp x26, x25, [sp, #-0x50]!
0x184a94068 <+4>: stp x24, x23, [sp, #0x10]
0x184a9406c <+8>: stp x22, x21, [sp, #0x20]
0x184a94070 <+12>: stp x20, x19, [sp, #0x30]
0x184a94074 <+16>: stp x29, x30, [sp, #0x40]
0x184a94078 <+20>: add x29, sp, #0x40 ; =0x40
0x184a9407c <+24>: sub sp, sp, #0x10 ; =0x10
0x184a94080 <+28>: mov x24, x1
; 在_CF_forwarding_prep_0函数里,sp保存到了x0里面,这里又将地址赋值给了x20
0x184a94084 <+32>: mov x20, x0
0x184a94088 <+36>: adrp x8, 174263
0x184a9408c <+40>: ldr x8, [x8, #0x770]
0x184a94090 <+44>: ldr x8, [x8]
0x184a94094 <+48>: stur x8, [x29, #-0x48]
; 从x20的地址开始取16位字节分别存到x19和x22,在在_CF_forwarding_prep_0函数里sp开始的16个字节分别存了self和_cmd,所以x19就是self,x22就是_cmd
0x184a94098 <+52>: ldp x19, x22, [x20]
-> 0x184a9409c <+56>: tbz x19, #0x3f, 0x184a940b8 ; <+84>
0x184a940a0 <+60>: ubfx x8, x19, #60, #3
0x184a940a4 <+64>: cmp x8, #0x7 ; =0x7
0x184a940a8 <+68>: ubfx x9, x19, #52, #8
0x184a940ac <+72>: add x9, x9, #0x8 ; =0x8
0x184a940b0 <+76>: csel x8, x8, x9, ne
0x184a940b4 <+80>: cbz w8, 0x184a9441c ; <+952>
;将self移到x0寄存器
0x184a940b8 <+84>: mov x0, x19
;调用object_getClass函数,获取Class对象,返回结果保存在x0
0x184a940bc <+88>: bl 0x183c466c4 ; object_getClass
; 将返回结果保存在x21
0x184a940c0 <+92>: mov x21, x0
; 根据class对象得到字符串
0x184a940c4 <+96>: bl 0x183c51810 ; class_getName
0x184a940c8 <+100>: mov x23, x0
0x184a940cc <+104>: adrp x8, 199366
; 得到SEL对象forwardingTargetForSelector:并保存到x25
0x184a940d0 <+108>: ldr x25, [x8, #0x340]
; 将class对象保存到x0
-> 0x184a940d4 <+112>: mov x0, x21
; 将sel对象保存到x1
0x184a940d8 <+116>: mov x1, x25
; 调用class_respondsToSelector方法,是否实现了得到SEL对象forwardingTargetForSelector:方法
0x184a940dc <+120>: bl 0x183c47550 ; class_respondsToSelector
0x184a940e0 <+124>: cbz w0, 0x184a94100 ; <+156>
0x184a940e4 <+128>: mov x0, x19
0x184a940e8 <+132>: mov x1, x25
-> 0x184a940ec <+136>: mov x2, x22
; 如果实现了就调用得到SEL对象forwardingTargetForSelector:方法
0x184a940f0 <+140>: bl 0x183c5c900 ; objc_msgSend
; 后面流程大致差不多,如果没实现得到SEL对象forwardingTargetForSelector:方法,就看下有没有methodSignatureForSelector方法,如果实现了methodSignatureForSelector方法,看下有没有调用forwardInvocation:....

  这部分流程总结如下:

  • 再次从缓存查找一次。
  • 如果缓存还是没找到,去类对象的方法表里查找方法,如果找到,就保存到缓存,并执行这个方法。
  • 如果类对象方法表里也没找到,就先去父类的缓存表里找,如果缓存表也没找到,就取找父类的方法表,如果找到,同样缓存方法到缓存,如果还是没找到,继续往上一层父类查找。
  • 以此类推,直到找到基类,即NSObject类的方法表。
  • 到了基类还是没找到,那么就先判断自己是不是元类,不是元类的话调用resolveInstanceMethod方法;是元类的话,先调用resolveClassMethod方法,接着去类的方法表一层一层查找有没有实现这个实例方法,如果也没找到就调用resolveInstanceMethod方法。
  • 如果resolveInstanceMethod方法或者resolveClassMethod方法也没被调用,开启转发流程。
  • 先调用forwardingTargetForSelector,如果这个方法返回nil,继续调用methodSignatureForSelector,如果返回不为空继续调用forwardInvocation;如果还是为空,调用doesNotRecognizeSelector,则闪退报错

0x04 Runtime的初始化

  这部分内容在Category文章里粗略讲述过一遍,这里将会带着源码详细走下初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
lock_init();
exception_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

  load_images函数其实就是调用类的+load方法,这里不重复介绍,Category文章里讲的很详细了。

1
2
3
4
5
6
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}

  我们直接看如何加载Mach-O信息,通过map_images函数获取所有的类相关的数据。

1
2
3
4
5
6
void  map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
// 之前做的操作,就是读取Mach-O的信息
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}

  Mach-O信息加载进来后,就要从数据段里读取相应的内容,并将构建成我们具体使用的类,我们的类,协议,分类等 都在数据段里,如下图。

数据段信息示意图

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
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
TimeLogger ts(PrintImageTimes);

runtimeLock.assertWriting();

#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++

if (!doneOnce) {
doneOnce = YES;

if (DisableTaggedPointers) {
disableTaggedPointers();
}

if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}

// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

ts.log("IMAGE TIMES: first time tasks");
}


// 获取类的信息
for (EACH_HEADER) {
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}

bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
// 从数据段的__objc_classlist里查找有哪些类,项目里有多少类,这里就有多少
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
// 得到类
Class cls = (Class)classlist[i];
// 以类名有key,类为value注册进一个类映射表
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

if (newCls != cls && newCls) {
// 跟objc_getFutureClass有关,这里可以忽略
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}

ts.log("IMAGE TIMES: discover classes");

// 修复类列表和懒加载类列表还没映射的情况
if (!noClassesRemapped()) {
for (EACH_HEADER) {
// 获取被引用的类,只有项目里被import的才会在这里出现
// 所以_getObjc2ClassList得到的总数 - _getObjc2ClassRefs得到的总数 = 项目中没有被用到的类,可以做优化参考。
Class *classrefs = _getObjc2ClassRefs(hi, &count);
// 在类映射表重新映射
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// 重新映射被引用的父类
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}

ts.log("IMAGE TIMES: remap classes");

// 获取被引用的方法列表
static size_t UnfixedSelectors;
sel_lock();
for (EACH_HEADER) {
if (hi->isPreoptimized()) continue;

bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
// 在方法映射表中注册
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
sel_unlock();

ts.log("IMAGE TIMES: fix up selector references");

// 读取协议列表
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();

protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
// 在协议映射表中注册
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}

ts.log("IMAGE TIMES: discover protocols");

// 获取被引用的协议,并重新映射
for (EACH_HEADER) {
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}

ts.log("IMAGE TIMES: fix up @protocol references");

// 实现+load的类列表
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 构造类信息,只有被构造好的类才可以被使用
realizeClass(cls);
}
}

ts.log("IMAGE TIMES: realize non-lazy classes");

// 获取分类列表
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();

for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
// 如果类映射表里没有分类对应的类,报错
if (!cls) {
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}

// 处理分类
// 首先,注册分类到其目标类
// 其次,重建类的方法表、协议表和成员变量表
bool classExists = NO;
// 实例信息
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 将分类表插入到分类映射表里,供后面取出使用加入到类的方法表、协议表等
// 因为此时类的结构还没被构造完成,就算要加也加不进去,所以先保存下
addUnattachedCategoryForClass(cat, cls, hi);
// 如果类已经被构造好,比如有实现+load方法的类会被先构造好,那么这里就需要重建类的结构,把类的方法、协议等重新加入进类的方法表、协议表里,并且刷新
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// 类信息
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}

ts.log("IMAGE TIMES: discover categories");

// 勾建所有类的信息,以便其可以被使用
if (DebugNonFragileIvars) {
realizeAllClasses();
}
#undef EACH_HEADER
}

// 动态链接所有的类
static void realizeAllClasses(void)
{
runtimeLock.assertWriting();

header_info *hi;
for (hi = FirstHeader; hi; hi = hi->getNext()) {
realizeAllClassesInImage(hi);
}
}

static void realizeAllClassesInImage(header_info *hi)
{
runtimeLock.assertWriting();

size_t count, i;
classref_t *classlist;

if (hi->areAllClassesRealized()) return;

classlist = _getObjc2ClassList(hi, &count);
// 动态链接类,其类处于可用状态
for (i = 0; i < count; i++) {
realizeClass(remapClass(classlist[i]));
}

hi->setAllClassesRealized(YES);
}

static Class realizeClass(Class cls)
{
runtimeLock.assertWriting();

const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;

if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));


// 获取RO信息
ro = (const class_ro_t *)cls->data();
// 之前类的信息是只读的,这时候构建成可读可写的
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);

isMeta = ro->flags & RO_META;

rw->version = isMeta ? 7 : 0;


// 父类和元类也需要构建
supercls = realizeClass(remapClass(cls->superclass));
metacls = realizeClass(remapClass(cls->ISA()));

// 更新被重新映射的父类和元类
cls->superclass = supercls;
cls->initClassIsa(metacls);

// 跳转实例变量的偏移和布局
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);

cls->setInstanceSize(ro->instanceSize);

if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}

// 链接这个类到父类的父子关系表中
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}

// 将分类数据,比如方法表、协议表加载进类的方法表、协议表中。是前插操作,具体分类的文章里有说够
methodizeClass(cls);

return cls;
}

static void methodizeClass(Class cls)
{
runtimeLock.assertWriting();

bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;

// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}

// 附加方法
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
// 附加属性
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
// 附加协议
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}

// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}

// 附加分类信息
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);

if (PrintConnecting) {
if (cats) {
for (uint32_t i = 0; i < cats->count; i++) {
_objc_inform("CLASS: attached category %c%s(%s)",
isMeta ? '+' : '-',
cls->nameForLogging(), cats->list[i].cat->name);
}
}
}

if (cats) free(cats);
}

  通过上面的源代码,可以知道,我们的类的都是从Mach-O得到的,一开始还没被动态链接,所以这个类是不能被使用的,整个初始化流程可以总结如下:

  • 通过Mach-O数据区里的__objc_classlist段,获取所有类,并注册进一个类映射表,通过这个表以后可以根据类名可以很快的得到这个类。
  • 读取数据区__objc_selrefs段,获取所有被引用的方法,并注册进一个方法映射表
  • 读取数据区__objc_protolist段,获取所有协议,并注册进一个协议映射表
  • 读取数据区__objc_nlclslist段,获取所有实现了+(void)load的类,并提前链接好类,使得这个类已经准备好被使用
  • 读取数据区__objc_catlist段,获取所有分类,并注册进一个分类映射表
  • 链接所有类,使得所有类做好被使用的准备。其中包括开辟class_rw_t区,使得类的数据可以即可读又可以写,并且排列好父子关系链,最后将分类的信息前插进类的各个信息表中。

0x05 @property分析

  @property定义的属性,我们知道在不是分类的情况下,会自动生成getter、setter和成员变量。其实这一切都是在编译器完成的,虽然这块不涉及runtime,但是前面我们知道类构建的时候会基于class_ro_t结构重新生成一个class_rw_t结构。而class_ro_t保存的其实就是编译期类的信息,其中包括属性拆解后的信息。

  定义测试代码如下

1
2
3
4
5
6
7
8
@interface Animal : NSObject

@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;

- (void)eat;

@end

  clang转成c++代码看下

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
61
62
63
64
65
66
67
68
69
70
71
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};

struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};

// 成员变量表
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2,
// 属性age和name变成了_age和_name的成员变量
{{(unsigned long int *)&OBJC_IVAR_$_Animal$_age, "_age", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_Animal$_name, "_name", "@\"NSString\"", 3, 8}}
};

// 方法表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_INSTANCE_METHODS_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
// 生成对应的setter和get方法
{{(struct objc_selector *)"age", "i16@0:8", (void *)_I_Animal_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_Animal_setAge_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Animal_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Animal_setName_}}
};
// 属性表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"age","Ti,N,V_age"},
{"name","T@\"NSString\",C,N,V_name"}}
};

// 初始化_class_ro_t结构
static struct _class_ro_t _OBJC_CLASS_RO_$_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0, __OFFSETOFIVAR__(struct Animal, _age), sizeof(struct Animal_IMPL),
0,
"Animal",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Animal,
0,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Animal,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Animal,
};

  通过转换的代码可以很清楚看到,属性的确是在编译期被拆解到class_ro_t结构的各个表中。我们看下成员变量表的初始化

1
2
3
4
5
6
7
8
9
10
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2, // 表内元素个数
{{(unsigned long int *)&OBJC_IVAR_$_Animal$_age, "_age", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_Animal$_name, "_name", "@\"NSString\"", 3, 8}}
};

  ivar_list装的是ivar_t,其结构如下,拿上面第一个ivar_t结构举例:

1
2
3
4
5
6
7
8
9
10
11
12
struct ivar_t {
int32_t *offset; // 成员的偏移地址,对应&OBJC_IVAR_$_Animal$_age
const char *name; // 名字,对应"_age"
const char *type; // 类型,对应"i"
uint32_t alignment_raw; //内存对齐, 对应2
uint32_t size; // 对应4

uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};

  OBJC_IVAR_$_Animal$_age表示成员的偏移地址,这样做的好处是只要拿到实例的地址,再加上偏移地址就能直接访问成员变量,快速的进行取值和赋值。

1
extern "C" unsigned long int OBJC_IVAR_$_Animal$_age __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Animal, _age);

  其他方法表和属性表初始化也类似,这里不做复述,但是我们看到在方法表中还有这样的代码

1
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_Animal_age},

  i16@0:8是什么样的格式,这其实是@encode指令,可以将具体的类型表示成字符串编码。

encode示意图

  那么i16@0:8是什么意思呢?

i:表示返回int类型

16:表示整个方法占据16个字节

@:表示第一个参数,即对象,方法中默认带两个参数self和_cmd,这里就是指self

0:表示其从偏移位置0开始的8个字节,之前说过这个方法占据16个字节,self占据其中8个字节

::表示第二个参数,是一个方法,即_cmd

8:表示偏移位置8开始的8个字节,即方法占据16个字节的后8个字节。

  v20@0:8i16再来一个setter方法

v:表示返回void

20:表示整个方法占据20字节

@:表示第一个参数对象,即self,占据8个字节

0:表示对象从偏移值0开始占据8个字节

:表示第二个参数,是一个方法,即_cmd

8:表示方法是从偏移值8开始占据8个字节

i:表示第三个参数,int类型的参数

16:表示第三个参数从偏移值16开始占据4个字节

  所有参数加起来的确是20个字节。继续回到这个方法表中,_I_Animal_age就是函数的实际执行地址

1
{{(struct objc_selector *)"age", "i16@0:8", (void *)_I_Animal_age},

 这里也验证了默认带的两个参数:self、cmd。OBJC_IVAR_$_Animal$_age前面说过表示的是成员变量的偏移地址。,通过self+偏移地址直接拿到值

1
2
3
static int _I_Animal_age(Animal * self, SEL _cmd) { 
return (*(int *)((char *)self + OBJC_IVAR_$_Animal$_age));
}

  不同于getter,setter的执行函数内部是这样的,拿到成员变量偏移地址后赋值

1
2
3
static void _I_Animal_setAge_(Animal * self, SEL _cmd, int age) { 
(*(int *)((char *)self + OBJC_IVAR_$_Animal$_age)) = age;
}

  如果我自己实现了setter方法,代码又会如何变化

1
2
3
4
5
6
7
- (void)setAge:(int)age {

}

// 转换代码
static void _I_Animal_setAge_(Animal * self, SEL _cmd, int age) {
}

  setter方法就变成了自己定义的方法了,如果里面不写_age = age,那不会对成员变量_age进行赋值。


  在回来说下属性表,之前说过对方法的定义上,苹果有一套自己的@encode指令,属性同样也有这样的@endcode指令

1
2
3
4
5
6
7
8
9
10
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"age","Ti,N,V_age"},
{"name","T@\"NSString\",C,N,V_name"}}
};

  其参照表如下

encode示意图

  Ti,N,V_age的理解如下

T:类型,后面紧跟着的就是对应类型

i:int类型

N:nonatomic

V_age:对应的变量名是_age

  T@\"NSString\",C,N,V_name,对于这样的可以理解为如下,对于我们定义属性时的@property(nonatomic, copy) NSString *name是不是一一对应了。

T@\”NSString\”:类型是个OC对象,对象名是NSString

C:copy

N:nonatomic

V_name:对应的变量名是_name


  我们已经知道属性在编译期会自动被拆成成员变量、setter、getter以及相关属性信息放到各个对应的表中。那么我们在运行时想要创建一个属性,下面这样做可以吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (id)init{
if (self = [super init]) {
class_addIvar([self class], "_name", sizeof(NSString *), log2(sizeof(NSString*)), @encode(NSString *));
objc_property_attribute_t attrs[] = { { "T", "@\"NSString\"" }, { "C", "" }, { "V", "_name" } };
class_addProperty([self class], "name", attrs, 3);
class_addMethod([self class], @selector(name), (IMP)nameGetter, "@@:");
class_addMethod([self class], @selector(setName:), (IMP)nameSetter, "v@:@");
}
return self;
}

NSString *nameGetter(id self, SEL _cmd) {
Ivar ivar = class_getInstanceVariable([self class], "_name");
return object_getIvar(self, ivar);
}

void nameSetter(id self, SEL _cmd, NSString *name) {
Ivar ivar = class_getInstanceVariable([self class], "_name");
id oldName = object_getIvar(self, ivar);
if (oldName != name) object_setIvar(self, ivar, [name copy]);
}

  答案是不可以,问题出在class_addIvar函数里,所在添加ivar的类有一个是RW_CONSTRUCTING的标签。

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
BOOL  class_addIvar(Class cls, const char *name, size_t size, 
uint8_t alignment, const char *type)
{
if (!cls) return NO;

if (!type) type = "";
if (name && 0 == strcmp(name, "")) name = nil;

rwlock_writer_t lock(runtimeLock);

assert(cls->isRealized());

if (cls->isMetaClass()) {
return NO;
}

// 来到这里返回NO
if (!(cls->data()->flags & RW_CONSTRUCTING)) {
return NO;
}

if ((name && getIvar(cls, name)) || size > UINT32_MAX) {
return NO;
}
}

  那么在哪里设置了这个标签,objc_allocateClassPair函数里调用的objc_initializeClassPair_internal函数里设置了这个标签,而objc_allocateClassPair则是我们动态生成一个类的时候用到的函数。

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
Class objc_allocateClassPair(Class superclass, const char *name, 
size_t extraBytes)
{
Class cls, meta;

rwlock_writer_t lock(runtimeLock);

if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) {
return nil;
}

// Allocate new classes.
cls = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);

// fixme mangle the name if it looks swift-y?
objc_initializeClassPair_internal(superclass, name, cls, meta);

return cls;
}

static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
......

// Set basic info

cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
......
}

  所以要想使用class_addIvar必须是动态生成的一个类,还不是一个在编译期就存在的类。

一个特殊的例子

  下面的例子中,程序是否可以正常运行,如果可以正常运行,那么得出的结果会是什么?

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
@interface Animal : NSObject

@property (nonatomic, copy) NSString *name;

- (void)print;

@end

@implementation Animal

- (void)print {
NSLog(@"动物的名字叫:%@", self.name);
}

@end

// 调用
int main(int argc, char * argv[]) {
NSString *a = @"占位数据,测试中....";

Class cls = [Animal class];
void *obj = &cls;
[(__bridge id)obj print];

return 0;
}

  运行程序后,我们可以看到print方法顺利被调用,但是结果有点出乎预料

1
动物的名字叫:占位数据,测试中....

  上面已经说过,我们方法的调用首先是取isa地址,取到isa地址后,isa地址与上 #0xffffffff8得到类对象地址。而此例中,我们获取到类对象后,然后&cls也取到了Animal类对象地址。从这里开始流程上就跟Animal实例对象调用print方法一致了,所以可以顺利调用print方法。

  为了我验证说的,我们首先实例化一个Aniaml对象

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
Animal *animal = [Animal new];

// 打印animal的地址
(lldb) p animal
(Animal *) $0 = 0x00000001d0007f00

// 这个地址保存的第一个位置就是isa,看过class结构都清楚isa是第一个成员
// isa的地址为0x000001a50000e59d
(lldb) x/4g 0x170000c80
0x1d0007f00: 0x000001a104b8659d 0x0000000000000000
0x1d0007f10: 0xbadd7e6de908bead 0x0000000000000000


// 与上& 0xffffffff8得到类对象地址
(lldb) p/x 0x000001a104b8659d & 0xffffffff8
(long) $1 = 0x0000000104b86598 // 记住这个类地址

// 我们打断点读取obj的地址为
(lldb) po obj
<Animal: 0x16b287978>

// obj保存的是cls指针的地址,实际上obj就是指向cls的。要想重新拿回cls对象的地址,需要看看这个地址保存的内容是什么
// 我们的类对象地址再次出现了
(lldb) x/4g 0x16b287978
0x16b287978: 0x0000000104b86598 0x00000001d0007f00
0x16b287988: 0x0000000104b84330 0x000000016b2879e0

  所以[(__bridge id)obj print];的时候,汇编代码中[x0]取的就是0x0000000104b865980x0000000104b86598 & 0xffffffff8还是0x0000000104b86598,剩下的步骤就跟[animal print]流程一样的,最后调用成功。

  我们的函数栈关系如下:

函数栈示意图

​ 那么,为什么打印结果会那么奇怪呢?我们先看下正常实例调用流程是怎么样的

函数栈示意图

  我们变量a指向 Animal实例对象所在的内存地址。调用self.name的时候,对象内存中第二个8个字节保存的就是变量_name的值,第一个8个字节肯定是isa。

  那么同样的,在我们这个例子里,我们的obj指向的是cls的地址,读取_name就是读取cls地址开始的第二个8个字节。

函数栈示意图

  我们证明下我们之前说的,断点打在print方法内,打印obj指向的内存的内容,0x000000010007e5a8是第一个8个字节,指的是isa指针;0x000000010007c340是第二个8个字节,就是我们字符串的内容,self.name读取的就是这块字符串。

1
2
3
4
5
6
7
8
(lldb) po self
<Animal: 0x16fd8f970>

(lldb) x/4g 0x16fd8f970
0x16fd8f970: 0x000000010007e5a8 0x000000010007c340
0x16fd8f980: 0x000000016fd8f9d8 0x0000000000000001
(lldb) po 0x000000010007c340
占位数据,测试中....

0x06 Super

  super在我们项目中用的熟的不能再熟了,比如调用父类的方法。这里我们先举个例子,有个继承自NSObject的类,下面的打印猜猜会是什么样的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface Animal()

@end

@implementation Animal

- (void)print {
NSLog(@"self class is %@\n", [self class]);
NSLog(@"self superclass is %@\n", [self superclass]);
NSLog(@"super class is %@\n", [super class]);
NSLog(@"super superclass is %@\n", [super superclass]);
}

@end

  这个细节很多人很容易忽视掉,我们发现[self class]、[self superclass]和[super class]、[super superclass]打印结果其实是一样的。

1
2
3
4
2018-05-22 20:06:17.169031+0800 testData[59785:156959813] self class is Animal
2018-05-22 20:06:17.169237+0800 testData[59785:156959813] self superclass is NSObject
2018-05-22 20:06:17.169353+0800 testData[59785:156959813] super class is Animal
2018-05-22 20:06:17.169484+0800 testData[59785:156959813] super superclass is NSObject

  那要解开这个原因,我们还是得先将代码转换一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void _I_Animal_print(Animal * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_Animal_cb9872_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_Animal_cb9872_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("superclass")));

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_Animal_cb9872_mi_2, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Animal"))}, sel_registerName("class")));

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_Animal_cb9872_mi_3, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Animal"))}, sel_registerName("superclass")));
}

// 有个__rw_objc_super结构
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};

  调用super的时候消息接收者实际了一个__rw_objc_super对象,赋值的时候__rw_objc_super对象的object实际是self,superClass实际是class_getSuperclass(objc_getClass(“Animal”)),即NSObject。

1
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Animal"))}, sel_registerName("class"))

  消息发送通过objc_msgSendSuper,但是真机调试中我们发现,实际调用的是objc_msgSendSuper2

1
0x100799638 <+64>:  bl     0x100799f64               ; symbol stub for: objc_msgSendSuper2

  同样objc_msgSendSuper2是一个汇编函数,我们需要看下其实现过程,这里其实只要看一行就够了

1
2
3
libobjc.A.dylib`objc_msgSendSuper2:
; x0即__rw_objc_super对象,取出16位分别给x0和x16寄存器,我们知道__rw_objc_super装了两个对象一个object和superClass,所以x0寄存器里装的就是self
0x183c5cb00 <+0>: ldp x0, x16, [x0]

  根据函数调用约定,x0装的是消息接收者,所以实际消息接收者是self。所以[self class]和[super class],实际消息接收者都是self,那么这两个的区别就是[self class]从自己的方法表开始查找方法,而[super class]是直接从父类的方法表开始查找方法。

0x07 isKindOfClass & isMemberOfClass

  有如下测试代码,其结果如何?

1
2
3
4
5
6
7
8
9
10
Animal *animal = [Animal new];

BOOL a = [animal isKindOfClass:[animal class]];
BOOL b = [animal isMemberOfClass:[animal class]];
BOOL c = [animal isKindOfClass:[NSObject class]];
BOOL d = [animal isMemberOfClass:[NSObject class]];
BOOL e = [Animal isKindOfClass:[Animal class]];
BOOL f = [Animal isMemberOfClass:[Animal class]];
BOOL g = [Animal isKindOfClass:[NSObject class]];
BOOL h = [Animal isMemberOfClass:[NSObject class]];

  不卖关子,结果如下

1
2
3
4
5
6
7
8
① a = YES;
② b = YES;
③ c = YES;
④ d = NO;
⑤ e = NO;
⑥ f = NO;
⑦ g = YES;
⑧ h = NO;

  分析为什么结果之前,我们先看下源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}

// object_getClass如果传入类对象,得到元类对象
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}

  源代码可以看出

1
2
3
4
5
6
7
实例方法:
isKindOfClass: 从自己类对象开始遍历父类对象,如果跟cls相等,就返回YES
isMemberOfClass:直接判断自己类对象与cls是否相等

类方法:
isKindOfClass: 从自己元类对象开始遍历父类元类对象,如果跟cls相等,就返回YES
isMemberOfClass:直接判断自己元类对象与cls是否相等

  根据上面得出的结论,我们的例子可以分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// animal是实例对象,其类对象是Animal,[animal class]即Animal,所以返回YES
BOOL a = [animal isKindOfClass:[animal class]];
// animal是实例对象,其类对象是Animal,[animal class]即Animal,所以返回YES
BOOL b = [animal isMemberOfClass:[animal class]];
// animal是实例对象,其类对象是Animal,遍历的时候回找到NSObject,所以返回YES
BOOL c = [animal isKindOfClass:[NSObject class]];
// animal是实例对象,其类对象是Animal,[NSObject class]即NSObject,两者不相等,所以返回NO
BOOL d = [animal isMemberOfClass:[NSObject class]];
// Animal是类对象,内部是比较元类对象,而[Animal class]是类对象,遍历只是找此元类对象的父类,两者肯定不相等,所以返回NO
BOOL e = [Animal isKindOfClass:[Animal class]];
// Animal是类对象,内部是比较元类对象,而[Animal class]是类对象,两者比较肯定不相等,所以返回NO
BOOL f = [Animal isMemberOfClass:[Animal class]];
// Animal是类对象,内部是比较元类对象,而[NSObject class]是类对象,遍历的时候查找元类对象的父类,然而又因为NSObject是基类,我们知道元类查找父类的时候,当找到元类的基类也没找到的话,会指向类对象,所以Animal元类对象查找super的时候找到NSObject元类对象的时候,发现还是不匹配,会继续匹配NSObject的类对象,而这时[NSObject class]就是NSObject类对象,所以相等,返回YES。
BOOL g = [Animal isKindOfClass:[NSObject class]];
// 因为没有遍历,元类对象跟类对象肯定不相等,所以返回NO
BOOL h = [Animal isMemberOfClass:[NSObject class]];

0x08 方法交换

  首先看下方法的结构信息,包含了函数实现的具体地址IMP。

1
2
3
4
5
6
struct method_t {
SEL name;
const char *types;
IMP imp;
......
};

  所以其实很好猜了,交换imp地址就能完成方法交换操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void method_exchangeImplementations(Method m1, Method m2)
{
if (!m1 || !m2) return;

rwlock_writer_t lock(runtimeLock);
// 交换
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;
// 缓存刷新下
flushCaches(nil);

updateCustomRR_AWZ(nil, m1);
updateCustomRR_AWZ(nil, m2);
}

0x09 Hook Block

  我们知道可以通过method_exchangeImplementations可以hook我们OC方法。那么如果我想要hook block又该如何实现呢?

  之前Block篇里我们知道block结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

struct __xxxx_block_desc_0 {
size_t reserved;
size_t Block_size;
// 下面两个是可选的,是捕获对象的时候会被加入到这个结构
void (*copy)(struct __xxxx_block_impl_0*, struct __xxx_block_impl_0*);
void (*dispose)(struct __xxxx_block_impl_0*);
}

struct __xxx_block_impl_0 {
struct __block_impl impl;
struct __xxx_block_desc_0* Desc;
}

  通过官方的ABI,我们看到对其完整定义如下,signature描述了方法的参数类型和返回值类型等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Block_literal_1 {
void *isa;
int flags; // 标志位
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved;
unsigned long int size;
// optional helper functions
void (*copy_helper)(void *dst, void *src);
void (*dispose_helper)(void *src);

const char *signature;
} *descriptor;
};

  所以我们可以自己定义一个这样的结构体,然后将block对象强转为这样的结构体,这样我们就可以拿到block对象的信息。

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
typedef struct _blockDescription {
size_t reserved;
size_t block_size;
}*BlockDescription;

typedef struct _blockMemoryDescription {
void (*copy)(void *dst, void *src);
void (*dispose)(void *blockImpl);
}*BlockMemoryDescription;

typedef struct _blockSignDescription {
char *signature;
}*BlockSignDescription;

typedef struct _hBlock{
void *isa;
int flags;
int reserved;
void *FuncPtr;
BlockDescription description;
}HBlock, *PHBlock;

// 对应block对象的flag成员
enum {
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
BLOCK_IS_GLOBAL = (1 << 28),
BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30),
};

  我们知道进入消息转发流程后,会调用NSBlockmethodSignatureForSelectorhookForwardInvocation方法,NSBlock是官方的对象所以我们不能进行修改,所以需要替换methodSignatureForSelectorhookForwardInvocation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define EXCHANGE_METHOD(originalM, replaceM) \
{ Class class = [NSObject class]; \
Class swizzledClass = [HookBlock class];\
SEL originalSelector = @selector(originalM);\
SEL swizzledSelector = @selector(replaceM); \
Method originalMethod = class_getInstanceMethod(class, originalSelector); \
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector); \
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); \
if (success) { \
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); \
} else { \
method_exchangeImplementations(originalMethod, swizzledMethod);\
}\
}

- (void)hookBlock:(id)block {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
EXCHANGE_METHOD(methodSignatureForSelector:, hookMethodSignatureForSelector:)
EXCHANGE_METHOD(forwardInvocation:, hookForwardInvocation:)
});
// 强转为自定义的block结构
PHBlock hBlock = (__bridge PHBlock)(block);
}

替换原来的block实现

  现在有这样一个需求,我拦截block后,需要替换其原本实现,我们原本定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char * argv[]) {

void(^testBlock)(void) = ^{
NSLog(@"很高兴认识你");
};

HookBlock *hook = [HookBlock new];
[hook hookBlock:testBlock];

testBlock();

return 0;
}

  如果正常调用,将会打印很高兴认识你,我们现在想要改变其打印为很讨厌认识你

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)hookBlock:(id)block {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
EXCHANGE_METHOD(methodSignatureForSelector:, hookMethodSignatureForSelector:)
EXCHANGE_METHOD(forwardInvocation:, hookForwardInvocation:)
});
PHBlock hBlock = (__bridge PHBlock)(block);

Method replaceMethod = class_getInstanceMethod([self class], @selector(replaceBlock));
IMP replaceMethodIMP = method_getImplementation(replaceMethod);
hBlock->FuncPtr = replaceMethodIMP;
}

- (void)replaceBlock {
NSLog(@"很讨厌认识你");
}

  运行后,可以看到成功替换了实现

1
2018-05-23 20:55:31.041306+0800 testData[29327:25955322] 很讨厌认识你

打印block的参数

  我们再次改变需求,这次我们想要先打印下block的参数,然后再调用其实现方法。

  先看下初始化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char * argv[]) {

void(^testBlock)(int a, int b) = ^(int a, int b){
NSString *result = [NSString stringWithFormat:@"%d + %d = %d", a, b, a + b];
NSLog(@"%@", result);
};

HookBlock *hook = [HookBlock new];
[hook hookBlock:testBlock];

testBlock(1, 2);

return 0;
}

  我们首先拦截block,然后进入消息转发流程,根据NSMethodSignature可以获得参数类型,根据NSInvocation可以获得参数值,所以我们的实现如下

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
61
62
63
64
65
66
67
68
69
70
71
72
73
- (void)hookBlock:(id)block {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
EXCHANGE_METHOD(methodSignatureForSelector:, hookMethodSignatureForSelector:)
EXCHANGE_METHOD(forwardInvocation:, hookForwardInvocation:)
});
PHBlock hBlock = (__bridge PHBlock)(block);

// 保存原来实现函数地址
objc_setAssociatedObject(block, "originImp", [NSValue valueWithPointer:hBlock->FuncPtr], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 指向进入消息转发流程的函数地址
hBlock->FuncPtr = _objc_msgForward;
}

- (NSMethodSignature *)hookMethodSignatureForSelector:(SEL)aSelector {
PHBlock block = (__bridge PHBlock)self;

uint8_t *desc = (uint8_t *)block->description;
desc += sizeof(struct _blockDescription);

if(block->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct _blockMemoryDescription);
}

BlockSignDescription signDesc = (BlockSignDescription)desc;
const char * signature = signDesc->signature;
return [NSMethodSignature signatureWithObjCTypes:signature];
}

- (void)hookForwardInvocation:(NSInvocation *)anInvocation {
PHBlock block = (__bridge PHBlock)self;

NSMethodSignature *methodSignature = [anInvocation methodSignature];
NSInteger numberOfArguments = [methodSignature numberOfArguments];

for (NSInteger i = 1; i < numberOfArguments; i++) {
const char *argTypeName = [methodSignature getArgumentTypeAtIndex:i];

switch (argTypeName[0]) {
case '@': {
__unsafe_unretained id type;
[anInvocation getArgument:&type atIndex:i];
NSLog(@"%@", [NSString stringWithFormat:@"第%@个参数值为%@", @(i), type]);
}break;

#define DATA_TYPE_ARG(_typeSymbol, _type) \
case _typeSymbol: { \
_type type; \
[anInvocation getArgument:&type atIndex:i]; \
NSString *result = [NSString stringWithFormat:@"第%@个参数值为%@", @(i), @(type)]; \
NSLog(@"%@", result); \
}break;


DATA_TYPE_ARG('i', int);
DATA_TYPE_ARG('I', unsigned int);
DATA_TYPE_ARG('l', long);
DATA_TYPE_ARG('L', unsigned long);
DATA_TYPE_ARG('q', long long);
DATA_TYPE_ARG('Q', unsigned long long);
DATA_TYPE_ARG('f', float);
DATA_TYPE_ARG('d', double);
DATA_TYPE_ARG('B', BOOL);
DATA_TYPE_ARG('c', char);
DATA_TYPE_ARG('C', unsigned char);
}
}

// 重新调用自己的实现
NSValue *pointerValue = objc_getAssociatedObject(anInvocation.target, "originImp");
block->FuncPtr = pointerValue.pointerValue;
[anInvocation invoke];
}