组件化分发生命周期 - AOP 方案(libffi 实现)

组件化分发生命周期 方案实现探索系列文章:
1、组件化分发生命周期
2、组件化分发生命周期 - AOP 方案
3、组件化分发生命周期 - AOP 方案(libffi 实现)

background

在上一篇 组件化分发生命周期 - AOP 方案 文章里,介绍了通过 Aspects 这个库,面向切面实现分发 UIApplicationDelegate 方法。

再简单重复介绍下分发思路:

  1. hook UIApplication 的 setDelegate: 方法,拿到 AppDelegate 实例

2)遍历 AppDelegate 的方法列表,判断是否是 UIApplicationDelegate 的协议方法

3)对以上遍历得到的方法一一进行 hook

4)在被 hook 方法执行的之前和之后,调用我们想要分发的那些对象对应的方法

在以上第 3) 步里,是通过 Aspects 实现的 hook。Aspects hook 方法可以返回 NSInvocation 对象,这个对象里包含着 hook 对象,hook 方法,方法参数个数、参数类型等信息,恰好可以拿这些信息去调用目标分发对象的同名方法。

Aspects 的实现原理是使方法调用(消息发送)立马进入消息转发阶段,在消息转发的最后阶段:forwardInvocation: 方法里拿到当前所 hook 方法的 NSInvocation 对象,并进行原方法调用和 block 的调用。

这种实现原理会导致原对象方法的调用要经历消息转发阶段,方法调用的性能差了一些。而且工程中如果全局 hook 了 NSObjectforwardingTargetForSelector:resolveInstanceMethod: 等方法,监听 unrecognized selector sent to instance 崩溃类型的话,则会捕获到 Aspects 的 hook 过程。

所以在这篇文章里,将采用 libffi 实现上述的第 3) 步的 hook 操作,替换 Aspects 的实现。 libffiAspects 的好处是,hook 操作是直接交换两个 IMP,和原生的 Method Swizzling 优势一样。而且同样能拿到 hook 方法的参数个数、参数类型等信息。

libffi preview

libffi 相当于 C 语言上的 runtime,拥有动态调用 C 方法及 OC 方法的能力。简单介绍下用法吧,因为本篇文章的重点不在解析 libffi 及用法,所以这里只做简单的使用说明。

例如,调用 C 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int c_func(int a , int b) {
int sum = a + b;
return sum;
}

- (void)libffi_call_c_func {
ffi_cif cif;
ffi_type *argTypes[] = {&ffi_type_sint, &ffi_type_sint};
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_sint, argTypes);

int a = 1;
int b = 2;
void *args[] = {&a, &b};
int retValue;
ffi_call(&cif, (void *)c_func, &retValue, args); // retValue = 3
}

调用 OC 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (int)oc_func:(int)a b:(int)b {
int sum = a + b;
return sum;
}

- (void)libffi_call_oc_func {
SEL selector = @selector(oc_func:b:);
NSMethodSignature *signature = [self methodSignatureForSelector:selector];

ffi_cif cif;
ffi_type *argTypes[] = {&ffi_type_pointer, &ffi_type_pointer, &ffi_type_sint, &ffi_type_sint};
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, (uint32_t)signature.numberOfArguments, &ffi_type_sint, argTypes);

int arg1 = 1;
int arg2 = 2;
void *args[] = {&self, &selector, &arg1, &arg2};
int retValue;
IMP func = [self methodForSelector:selector];
ffi_call(&cif, func, &retValue, args); // retValue = 3
}

函数调用 ffi_call 方法需要传入:1、函数模版(ffi_cif),2、函数指针,3、返回值,4、参数数组 这四个参数字段。
重点在于 函数模版(ffi_cif)。用 ffi_cif 生成一套函数模版,这个模版定义了调用一个函数时,这个函数的:1、参数个数(int),2、返回值类型(ffi_type),3、包含各个入参类型(ffi_type)的数组

这样,libffiffi_call 方法就可以根据这个传入的 函数模版(ffi_cif)去调用函数了。

其中,调用 OC 方法的话比较特殊,像 objc_msgSend 方法一样,第一位参数是函数调用者本身,第二位参数是调用的方法 SEL。

1
void objc_msgSend(void /* id self, SEL op, ... */ )

是不是看起来 libffi 的使用和 NSInvocation 很像。

libffi hook

除了动态调用 C & OC 方法外,libffi 提供的另外一个关键的能力是:
通过 ffi_closure_alloc 方法创建一个关联着函数指针(IMP)的闭包(closure
通过 ffi_prep_closure_loc 函数给 cif 关联上这个闭包,并传入一个函数实体(fun)和刚才闭包里的 IMP

这样,当 IMP 被执行的时候,就会执行函数实体(fun),并且能在这个函数里拿到:1、cif,2、返回值,3、参数地址,4、自定义的关联数据(userdata
例如:

1
2
3
4
5
6
7
8
9
10
void *newIMP = NULL;
ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), (void **)&newIMP);
ffi_status prepClosureStatus = ffi_prep_closure_loc(closure, cif, holo_lifecycle_ffi_closure_func, (__bridge void *)info, newIMP);
if (prepClosureStatus != FFI_OK) {
return;
}

static void holo_lifecycle_ffi_closure_func(ffi_cif *cif, void *ret, void **args, void *userdata) {

}

有了这样的能力,我们就可以将上述的 newIMP 与我们要 hook 的方法 IMP 交换,这样调用 hook 方法的时候,就会执行到 holo_lifecycle_ffi_closure_func 方法,在这个方法里可以拿到原方法的参数个数、参数类型能信息,我们只要在这个方法里再调用一下原方法的 IMP,并在之前和之后分别调用要转发的目标对象的同名方法即可。

其中,还需要额外指出的一点是,在动态 hook 方法的时候,如何根据 C&OC 方法里的参数类型转换成 ffi_type 呢?
苹果的官方文档 Objective-C Type Encodings 给出了参数 type encoding 字符串和参数类型的对应关系,也可以参考 NSHipster 的这篇文章:Type Encodings
其实每种类型,或者说相应字符,在 libffi 里也都存在着对应的 ffi_type 类型。这里直接拷贝了 Stinger 里的 ffi_type *_st_ffiTypeWithType(const char *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
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
@implementation UIApplication (HoloLifecycle)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

SEL originalSelector = @selector(setDelegate:);
SEL swizzledSelector = @selector(_holo_setDelegate:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, 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)_holo_setDelegate:(id <UIApplicationDelegate>)delegate {
unsigned count = 0;
Method *methods = class_copyMethodList([delegate class], &count);
for (int i = 0; i < count; i++) {
Method method = methods[i];
SEL sel = method_getName(method);
struct objc_method_description methodDesc = protocol_getMethodDescription(@protocol(UIApplicationDelegate), sel, NO, YES);
if (methodDesc.name != NULL && methodDesc.types != NULL) {
holo_lifecycle_hook_func(delegate, sel);
}
}

[self _holo_setDelegate:delegate];
}

void holo_lifecycle_hook_func(id obj, SEL sel) {
NSString *selStr = [@"holo_lifecycle_" stringByAppendingString:NSStringFromSelector(sel)];
const SEL key = NSSelectorFromString(selStr);
if (objc_getAssociatedObject(obj, key)) {
return;
}

HoloLifecycleHookInfo *info = [HoloLifecycleHookInfo new];
info.cls = [obj class];
info.sel = sel;

objc_setAssociatedObject(obj, key, info, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

Method method = class_getInstanceMethod([obj class], sel);
const char *typeEncoding = method_getTypeEncoding(method);
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:typeEncoding];

const unsigned int argsCount = method_getNumberOfArguments(method);
// 构造参数类型列表
ffi_type **argTypes = calloc(argsCount, sizeof(ffi_type *));
for (int i = 0; i < argsCount; ++i) {
const char *argType = [signature getArgumentTypeAtIndex:i];
ffi_type *arg_ffi_type = holo_lifecycle_ffi_type(argType);
NSCAssert(arg_ffi_type, @"HoloLifecycle: can't find a ffi_type: %s", argType);
argTypes[i] = arg_ffi_type;
}
// 返回值类型
ffi_type *retType = holo_lifecycle_ffi_type(signature.methodReturnType);

// 需要在堆上开辟内存,否则会出现内存问题 (HoloLifecycleHookInfo 释放时会 free 掉)
ffi_cif *cif = calloc(1, sizeof(ffi_cif));
// 生成 ffi_cfi 模版对象,保存函数参数个数、类型等信息,相当于一个函数原型
ffi_status prepCifStatus = ffi_prep_cif(cif, FFI_DEFAULT_ABI, argsCount, retType, argTypes);
if (prepCifStatus != FFI_OK) {
NSCAssert(NO, @"HoloLifecycle: ffi_prep_cif failed: %d", prepCifStatus);
return;
}

// 生成新的 IMP
void *newIMP = NULL;
ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), (void **)&newIMP);
ffi_status prepClosureStatus = ffi_prep_closure_loc(closure, cif, holo_lifecycle_ffi_closure_func, (__bridge void *)info, newIMP);
if (prepClosureStatus != FFI_OK) {
NSCAssert(NO, @"HoloLifecycle: ffi_prep_closure_loc failed: %d", prepClosureStatus);
return;
}

// 替换 IMP 实现
Class hookClass = [obj class];
SEL aSelector = method_getName(method);
if (!class_addMethod(hookClass, aSelector, newIMP, typeEncoding)) {
IMP originIMP = method_setImplementation(method, newIMP);
if (info->_originalIMP != originIMP) {
info->_originalIMP = originIMP;
}
}
}

static void holo_lifecycle_ffi_closure_func(ffi_cif *cif, void *ret, void **args, void *userdata) {
HoloLifecycleHookInfo *info = (__bridge HoloLifecycleHookInfo *)userdata;

// before
for (HoloBaseLifecycle *lifecycle in [HoloLifecycleManager sharedInstance].beforeInstances) {
holo_lifecycle_call_sel(lifecycle, info.sel, cif, args);
}

// call original IMP
ffi_call(cif, info->_originalIMP, ret, args);

// after
for (HoloBaseLifecycle *lifecycle in [HoloLifecycleManager sharedInstance].afterInstances) {
holo_lifecycle_call_sel(lifecycle, info.sel, cif, args);
}
}

static void holo_lifecycle_call_sel(HoloBaseLifecycle *lifecycle, SEL sel, ffi_cif *originalCif, void **originalArgs) {
if (![lifecycle respondsToSelector:sel]) {
return;
}

// 复用 cif,构造参数,重置 args[0]、args[1]
NSMethodSignature *signature = [lifecycle methodSignatureForSelector:sel];
NSUInteger argsCount = signature.numberOfArguments;
void **args = calloc(argsCount, sizeof(void *));
args[0] = &lifecycle;
args[1] = &sel;
memcpy(args + 2, originalArgs + 2, sizeof(*originalArgs)*(argsCount - 2));

IMP func = [lifecycle methodForSelector:sel];
ffi_call(originalCif, func, NULL, args);
}

NS_INLINE ffi_type *holo_lifecycle_ffi_type(const char *c) {
switch (c[0]) {
case 'v':
return &ffi_type_void;
case 'c':
return &ffi_type_schar;
case 'C':
return &ffi_type_uchar;
case 's':
return &ffi_type_sshort;
case 'S':
return &ffi_type_ushort;
case 'i':
return &ffi_type_sint;
case 'I':
return &ffi_type_uint;
case 'l':
return &ffi_type_slong;
case 'L':
return &ffi_type_ulong;
case 'q':
return &ffi_type_sint64;
case 'Q':
return &ffi_type_uint64;
case 'f':
return &ffi_type_float;
case 'd':
return &ffi_type_double;
case 'F':
#if CGFLOAT_IS_DOUBLE
return &ffi_type_double;
#else
return &ffi_type_float;
#endif
case 'B':
return &ffi_type_uint8;
case '^':
return &ffi_type_pointer;
case '@':
return &ffi_type_pointer;
case '#':
return &ffi_type_pointer;
case ':':
return &ffi_type_pointer;
case '{': {
// http://www.chiark.greenend.org.uk/doc/libffi-dev/html/Type-Example.html
ffi_type *type = malloc(sizeof(ffi_type));
type->type = FFI_TYPE_STRUCT;
NSUInteger size = 0;
NSUInteger alignment = 0;
NSGetSizeAndAlignment(c, &size, &alignment);
type->alignment = alignment;
type->size = size;
while (c[0] != '=') ++c; ++c;

NSPointerArray *pointArray = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory];
while (c[0] != '}') {
ffi_type *elementType = NULL;
elementType = holo_lifecycle_ffi_type(c);
if (elementType) {
[pointArray addPointer:elementType];
c = NSGetSizeAndAlignment(c, NULL, NULL);
} else {
return NULL;
}
}
NSInteger count = pointArray.count;
ffi_type **types = malloc(sizeof(ffi_type *) * (count + 1));
for (NSInteger i = 0; i < count; i++) {
types[i] = [pointArray pointerAtIndex:i];
}
types[count] = NULL; // terminated element is NULL

type->elements = types;
return type;
}
}
return NULL;
}

@end

完整实现代码参见:HoloLifecycle