组件化分发生命周期 方案实现探索系列文章:
1、组件化分发生命周期
2、组件化分发生命周期 - AOP 方案
3、组件化分发生命周期 - AOP 方案(libffi 实现)
在之前的文章( 组件化分发生命周期 )里已经介绍过了 组件化分发生命周期 的概念及理由,并总结了几种分发生命周期的方案,最后一节的方案本以为已经很完美了,但是在项目中实际应用的时候还是发现了问题。
问题
因为我们工程里用了 Firebase 打点,其中 Firebase 依赖的一个库 GoogleUtilities
也对 UIApplication 的 delegate 里的一些方法做了 hook 操作,对 APP 做了一些监听操作。以下是 GoogleUtilities
产生影响的关键代码:1
2
3
4
5
6
7
8
9
10
11
12+ (GULApplication *)sharedApplication {
if ([GULAppEnvironmentUtil isAppExtension]) {
return nil;
}
id sharedApplication = nil;
Class uiApplicationClass = NSClassFromString(kGULApplicationClassName);
if (uiApplicationClass &&
[uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
sharedApplication = [uiApplicationClass sharedApplication];
}
return sharedApplication;
}
其中 kGULApplicationClassName
就是 @"UIApplication"
。因为在上一种方案里我们将系统的 delegate 重设成了我们自己的类:GHLApplicationDelegate
,并通过消息转发的方式统一处理了原 AppDelegate
里的所有方法。
这样一来,Firebase 通过以上代码得到的 GULApplication
就变成了 GHLApplicationDelegate
,再去 hook application:didFinishLaunchingWithOptions:
等方法的时候就找不到这些方法了,即 Firebase 失去了相应的功能。
这里是 Firebase 的场景,其他第三方库有相应 hook 操作的话也会有相同的问题,而我们在实际开发过程中没法避免其他库的 hook 操作,甚至可能察觉不到问题的存在,所以之前提到的方案是有问题的。
分析
这样一想,原本的方法的确存在设计上的问题,我们只顾着自己的 hook,影响了其他第三方对 AppDelegate
的 hook 操作,这显然不合理。
为了解决以上的问题,不得不再次寻找方案。重新梳理下我们的需求:
在主工程的 AppDelegate
里执行 UIApplicationDelegate
的一系列生命周期的方法的时候,相应的各个 Pod 里也能有对应方法同时被执行,且不影响其他第三方对 AppDelegate
的 hook 操作。例如:主工程 AppDelegate
里的 application:didFinishLaunchingWithOptions:
方法被执行时,各个 Pod 里也能有对应的 application:didFinishLaunchingWithOptions:
方法被执行。
那我们也可以 hook AppDelegate
的各阶段方法,遍历执行各个 Pod 里的对应方法之后,再执行下原本的方法。这样大家都保证各自的 hook 链不会断掉就 ok 了。
解决方案:AOP
想起 Aspects 这个库。或许,AOP 是一种不错的解决方案。思路如下:
1)首先还是采用上一篇文章(组件化分发生命周期)的方案,在各个 Pod 里通过继承 HoloLifecycle
类的方式让其子类拥有执行 UIApplicationDelegate 生命周期方法的能力。
2)hook UIApplication
的 setDelegate:
方法,拿到 AppDelegate
3)遍历 AppDelegate
的方法列表,判断是否是 UIApplicationDelegate
的协议方法
4)使用 AOP 的方法,对以上遍历加判断筛选拿到的方法一一进行 hook
5)在被 hook 的每个方法执行前,遍历 HoloLifecycle
的所有子类,执行同名方法
以上就是根据需求想到的解决步骤,唯一的关键就在最后一步,执行 HoloLifecycle
类里的方法时,因为入参不确定,performSelector:withObject:
方法肯定是不适用了,可以用 NSInvocation
的方法转发消息,参考 Aspects
。
大体思路就是:
1)通过 Aspects
hook AppDelegate
方法,让被 hook 的方法不会被正常执行,而是走消息转发流程(Aspects
的思路)
2)在消息转发的最后一步 forwardInvocation:
方法里获取当前所 hook 方法的 NSInvocation
对象 A,在这个对象里存储着转发的方法名,参数值等信息
3)根据 HoloLifecycle
类对象(target)和想要分发的方法的方法名(selector),创建一个 NSInvocation
对象 B
4)遍历 NSInvocation
对象 A 的所有参数,把它们依次赋值给 NSInvocation
对象 B,并发送对象 B
遍历所有的 HoloLifecycle
类对象,循环执行以上步骤就可以了,这样就做到了 hook AppDelegate
各生命周期方法,在各方法执行前遍历执行 HoloLifecycle
类对象的对应同名方法,也就是:组件化分发生命周期 的最终效果。
一开始以为 Aspects
暴露的方法里没法获取所 hook 方法的 NSInvocation
,所以花了些力气参考 Aspects
的源码实现了一套方案。能够正常工作之后才发现 Aspects
的 hook 方法的 block 里可以拿到 AspectInfo
这个对象,而这个对象持有的 originalInvocation
属性,就是所 hook 方法走 forwardInvocation:
方法时拿到的 NSInvocation
。所以,最后借助 Aspects
的实现简单了很多,Aspects
已经完成了 hook 和返回 NSInvocation
的功能,我们主要完成以上 3)、4)步骤即可,以下是关键代码: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@implementation UIApplication (HoloLifecycle)
+ (void)load {
[self jr_swizzleMethod:@selector(setDelegate:) withMethod:@selector(_holo_setDelegate:) error:nil];
}
- (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) {
[self _aspect_hookSelectorWithDelegate:delegate sel:sel];
}
}
[self _holo_setDelegate:delegate];
}
- (void)_aspect_hookSelectorWithDelegate:(id <UIApplicationDelegate>)delegate sel:(SEL)sel {
[(NSObject *)delegate aspect_hookSelector:sel withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info) {
for (HoloLifecycle *lifecycle in [HoloLifecycleManager sharedInstance].subClasses) {
if ([lifecycle respondsToSelector:sel]) {
NSMethodSignature *signature = [lifecycle methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = lifecycle;
invocation.selector = sel;
NSUInteger numberOfArguments = signature.numberOfArguments;
if (numberOfArguments > info.originalInvocation.methodSignature.numberOfArguments) {
NSLog(@"lifecycle has too many arguments. Not calling %@", info);
continue;
}
void *argBuf = NULL;
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [info.originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
if (!(argBuf = reallocf(argBuf, argSize))) {
NSLog(@"Failed to allocate memory for lifecycle invocation.");
break;
}
[info.originalInvocation getArgument:argBuf atIndex:idx];
[invocation setArgument:argBuf atIndex:idx];
}
[invocation invoke];
if (argBuf != NULL) {
free(argBuf);
}
}
}
} error:nil];
}
@end
补充功能
1、参考青木的 TDFModuleKit,也给 HoloLifecycle
类添加了优先级方法,在子类里重写该方法返回合适的优先级即可决定执行顺序,
并且 HoloLifecycle
支持在 AppDelegate
方法之前、之后两阶段执行:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/// 调用优先级
typedef NS_ENUM(NSInteger, HoloLifecyclePriority) {
// 晚于 AppDelegate 执行
HoloLifecyclePriorityAfterLow = 50,
HoloLifecyclePriorityAfterMedium = 150,
HoloLifecyclePriorityAfterHigh = 250,
// 早于 AppDelegate 执行
HoloLifecyclePriorityBeforeLow = 500,
HoloLifecyclePriorityBeforeMedium = 750, // default
HoloLifecyclePriorityBeforeHigh = 1000,
};
/// 调用优先级
/// 子类重写该方法 return 合适的优先级
/// AppDelegate 的优先级为 300,若 HoloLifecycle 子类同样定义为 300,则先于 AppDelegate 执行
/// @return 合适的优先级,默认为 HoloLifecyclePriorityBeforeMedium
+ (HoloLifecyclePriority)priority;
2、使用 HoloLifecycleManager
类的以下方法可以添加 log 功能,方便监控各个方法的调用顺序和耗时:1
2
3/// 打印所有 HoloLifecycle 子类执行方法及耗时
/// 在 + load 方法里调用该方法,以保证该方法早于 UIApplicationDelegate 方法调用
- (void)logSelectorsAndPerformTime;
代码已开源至:HoloLifecycle,欢迎使用。