原文 : 与佳期的我的博客(gonghonglou.com)html
我不要你以为,我要我以为,听个人,组件化分发生命周期就这么写!ios
组件化分发生命周期是什么?就是将主工程的生命周期分发到各个组件里去。直观些的介绍则是:AppDelegate 遵循并实现了 UIApplicationDelegate 代理,其中包括 willFinishLaunchingWithOptions:
、didFinishLaunchingWithOptions:
、applicationWillEnterForeground:
、applicationDidEnterBackground:
等等方法,包含了主工程的各个阶段将执行的方法,咱们要作的就是在主工程的这些阶段方法被执行的时候,各个组件里相对应的阶段方法同时会被执行,这样,主工程和各个组件便共享了生命周期,git
至于为何要将主工程的生命周期分发到各个组件中,缘由有如下几点:github
一、替换 load 方法 由于 load 方法时机较早,全部不少时候会在 load 方法里执行注册,初始化等操做,但这也会致使 load 方法的滥用,将一些本能够靠后执行的操做提早执行了,可能引起 APP 启动耗时过长的问题,须要作 load 耗时监测,治理起来困难,因此不少团队是禁用 load 方法的。 将这些操做方法放到生命周期方法里去作显然更好,寻找合理的时机执行相应的操做,耗时能检测功能也比较好作。objective-c
二、解决 AppDelegate 臃肿问题 工程中不免有一系列的注册、初始化操做,好比:APP 性能检测、bug 收集、打点等一系列工具的注册;各类基础组件涉及的初始化或重置操做。 将这些操做放到组件本身的生命周期方法里去执行,避免了 AppDelegate 的臃肿,并且各基础组件与主工程解耦,开发维护更方便。数组
三、Debugger 类组件可插拔 某些 Debugger 类组件在工做前可能须要注册操做,将注册操做放在 Pod 本身的生命周期里。这样一来,对于 Debugger 类组件只须要在 Podfile 里控制加载形式,便可作到 Debug/Release 环境组件可插拔,如:缓存
pod 'DebuggerKit', :configurations => ['Debug']
复制代码
相比于将 AppDelegate 里的全部阶段方法分发出去,先介绍两种相对轻量的作法,也能作到和分发生命周期相似的能力:ruby
巧妙的通知注册app
+ (void)load {
__block id observer =
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidFinishLaunchingNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
[self setup]; // Do whatever you want
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
}
复制代码
很巧妙的方法,优势很明显:轻量!虽然侵入了 load 方法,不过若是没有 load 的滥用的话也能够接受,毕竟只是在 load 里执行了注册行为,具体的执行时机仍是 UIApplicationDidFinishLaunchingNotification
函数
缺点是该方法的响应是在 - application:didFinishLaunchingWithOptions:
调用完成后发送,时机无法精确控制,由于有的时候由于时机问题,咱们想让各类 Pod 里的注册操做在 AppDelegate 的 didFinishLaunchingWithOptions:
方法靠前执行,即先执行组件里的注册操做再执行 AppDelegate 里的操做。
参考原文:Notification Once
在编译时把数据(如函数指针)写入到可执行文件的 __DATA 段中,在运行时的某个阶段(如 willFinishLaunch)再从 __DATA 段取出数据进行相应的操做(调用函数)。
Kylin实现原理简述:Clang 提供了不少的编译器函数,它们能够完成不一样的功能。其中一种就是 section() 函数,section()函数提供了二进制段的读写能力,它能够将一些编译期就能够肯定的常量写入数据段。 在具体的实现中,主要分为编译期和运行时两个部分。在编译期,编译器会将标记了 attribute((section())) 的数据写到指定的数据段中,例如写一个 {key(key表明不一样的启动阶段), *pointer} 对到数据段。到运行时,在合适的时间节点,在根据key读取出函数指针,完成函数的调用。
这种方案呢,最好去写个专门的工具,如 Kylin,去实现 {key, *pointer} 对的注册和调用操做。对于使用方来讲,添加一种函数执行时,使用 Kylin 注册,并在 AppDelegate 的合理阶段调起方法。
除了以上方法以外还有一些比较“大型”的作法就是把 AppDelegate 的生命周期完整的分发出去:
这是我以前公司的作法,提早注册 Lifecycle 类(可实现 AppDelegate 的各阶段方法),在 AppDelegate 各阶段方法执行的同时遍历 lifecycle 类执行相应方法。具体的作法是,
1)项目中存在一份配置文件,文件里配置着各个 pod 的 Lifecycle 类名,该类里实现了 AppDelegate 的某几个阶段方法。
2)项目启动的时候加载这份配置文件,根据类名反射成 Lifecycle 类,将全部的类添加到一个数组中(LifecycleArray)。
3)在 AppDelegate 和 UIResponder 的继承中间加一个 MyAppDelegate 类(GHLAppDelegate : MyAppDelegate : UIResponder),该类拥有 AppDelegate 的全部方法,在每一个阶段的方法里遍历 LifecycleArray 数组,调用各个 Lifecycle 类的本阶段方法。
4)在 AppDelegate 的各阶段方法里首先调用一下 super 方法。
这样,在 AppDelegate 各阶段执行的时候就会执行父类方法,遍历全部 pod 里的 Lifecycle 类,执行相应方法,从而实现生命周期的分发。
这种作法的优势是没什么骚操做(姑且算优势吧),都是基本方法遍历调用,就一个反射操做也算经常使用吧。弊端就显而易见了:
1)须要注册行为。每添加一个 pod,想要为该 Pod 配置生命周期管理类的话都要去配置文件里注册一次。虽然项目稳定下来后 pod 基本不会变更,但使用起来总归不够理想,并且由于配置文件的存在,这种中心化的写法会致使代码臃肿,阅读维护困难。 2)侵入 AppDelegate 类。须要更改 AppDelegate 的父类,而且在 AppDelegate 的各阶段里调用 super。
由于上一种方案中存在的问题,因此我在想怎么作既能够不用注册,又不用侵入 AppDelegate 呢?Category!我想到这种方案:
1)新建 Lifecycle 类,用于向相应的 pod(第三步提到的建过度类的 Pod) 分发生命周期。该类拥有 AppDelegate 的全部生命周期方法。
2)为了避免侵入 AppDelegate,给 AppDelegate 添加分类(AppDelegate+Lifecycle),用于在相应阶段调用 Lifecycle 相应方法。在该分类里重写 AppDelegate 的各阶段方法,在各个方法里分别调用 Lifecycle 类的对应方法(这里其实也能够在分类里 hook 本类的方法来实现,可是为了写法方便 Demo 里的作法是使用了第四步提供的方法:遍历方法列表找到最后一个方法执行)。
3)对使用方来讲,只须要在本身的 pod 里新建 Lifecycle 类的分类(如:Lifecycle + Home、Lifecycle + Deatil 等),复写本类的方法,即 AppDelegate 的生命周期方法,这些 pod 里的这些分类的这些方法会被 Lifecycle 类所有执行。
4)怎么所有执行呢?多个分类会根据加载顺序互相覆盖方法,正常状况下只执行最后加载的那个分类的方法,由于最后加载的分类的方法被最后加到方法列表里,消息发送过程当中最早被找到。解决思路是:找到那些被覆盖的方法去执行对应的 IMP:
4.1)在 Lifecycle 本类里去遍历本类的方法列表,为了不无限循环,除了本类的方法(即方法列表的最后一个),对其余的同名 SEL 都执行对应 IMP:
+ (void)execCategorySelector:(SEL)selector forClass:(Class)class withParam1:(id)param1 param2:(id)param2 {
BOOL isFirst = NO;
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(class, &methodCount);
for (int i = methodCount - 1; i >= 0; i--) {
Method method = methods[i];
SEL sel = method_getName(method);
if ([NSStringFromSelector(sel) isEqualToString:NSStringFromSelector(selector)]) {
if (!isFirst) {
isFirst = YES;
} else {
IMP imp = method_getImplementation(method);
void (*func)(id, SEL,id,id) = (void *)imp;
func(self, sel, param1, param2);
}
}
}
free(methods);
}
复制代码
4.2)为了写法方便和统一,这里给第二步也提供了执行 AppDelegate 本类方法的实现。在 AppDelegate 的方法列表里寻找同名的的最后一个 SEL,执行对应的 IMP。
+ (void)execClassSelector:(SEL)selector forClass:(Class)class withParam1:(id)param1 param2:(id)param2 {
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(class, &methodCount);
for (int i = methodCount - 1; i >= 0; i--) {
Method method = methods[i];
SEL sel = method_getName(method);
if ([NSStringFromSelector(sel) isEqualToString:NSStringFromSelector(selector)]) {
IMP imp = method_getImplementation(method);
void (*func)(id, SEL,id,id) = (void *)imp;
func(self, sel, param1, param2);
break;
}
}
free(methods);
}
复制代码
这样就能调用到全部 Category 的生命周期方法,起到分发的效果。
优势便是: 1)对使用方来讲没必要注册,只需建立 Lifecycle 的分类 2)不侵入 AppDelegate 代码
缺点是: 1)要手动在主工程建立 AppDelegate 分类。 2)所添加的分类里的同名方法不会被覆盖,有反常识。 3)还有个缺点,也是为何在标题上加一个 ⚠️ 的缘由,是由于给一个类添加多个 Category,并分别覆盖本类方法时 Xcode 会提示 warning
Category is implementing a method which will also be implemented by its primary class
由于上一种方案中存在的问题,我在想还有什么更好的方案,只提供一个 Pod 组件,就能够完成全部操做的方案。而后找到了青木同窗的 组件化之组件生命周期管理 这篇文章,实现方案在文章里已经讲的很详细了,思路就是:
1)新建 Module 类,提供注册功能,而且能够设置优先级。使用方在本身的 Pod 继承该类建立本身的生命周期管理类,而且在 load 方法调用 Module 类的注册方法。
2)新建 UIApplication 的分类:UIApplication (Module)
,hook 掉 setDelegate: 方法,将代理设置给本身建立的类:ApplicationDelegateProxy。同时对包含全部注册类的数组根据优先级进行排序。
3)ApplicationDelegateProxy 类里不会实现 AppDelegate 里的那些方法,因此当系统来调用这些方法的时候,由于找不到 SEL 会进入消息转发过程:
3.1) -respondsToSelector:
:系统内部会调用这个方法。 判断是否实现了对应的 UIApplicationDelegate 代理方法。重写该方法结合 AppDelegate 以及全部注册的 Module 判断是否有相应实现。
3.2)-forwardingTargetForSelector:
: -respondsToSelector:
返回 YES ,便进入消息转发阶段,消息转发的第二步就是该方法。 判断要转发的方法是否为 UIApplicationDelegate 的代理方法,若是不是,而且 AppDelegate 能响应,把消息转发给 AppDelegate 去处理。
3.3)-methodSignatureForSelector:
和 -forwardInvocation:
:若是消息没有发给 AppDelegate,由本身来处理,将会这执行这些方法。 在这一步首先根据协议直接返回代理方法的签名,而后在 -forwardInvocation:
方法中,按照优先级,依次把消息转发给注册的模块。
3.4)消息转发中处理返回值为 BOOL 类型的状况。
这样就经过消息转发完成了生命周期的分发。已是很不错的实现了,对外部文件没有侵入,惟一的缺点就是须要一个注册操做,并且仍是在 load 方法里。
我在想有什么方法能够去掉这个注册操做?若是咱们让全部组件里控制生命周期的类都继承自 Lifecycle 类,那么咱们经过获取 Lifecycle 的全部子类就可以完成注册操做了。思路很简单:经过 runtime 获取全部注册的类,遍历这些类判断其父类是不是 Lifecycle 类。由于注册类的总数可能会很是大,为了不性能问题,将这个方法的调用控制在 Debug 模式下执行,将拿到的数组存储在本地,这样在 Release 环境下直接获取缓存数据便可。
照着这样的思路又实现了一份优化过的代码,收集注册子类:
- (instancetype)init {
self = [super init];
if (self) {
#if DEBUG
NSArray *stringArray = [self _findAllSubClass:[GHLLifecycle class]];
self.subClasses = [self _classArrayWithStringArray:stringArray];
[[NSUserDefaults standardUserDefaults] setObject:stringArray forKey:kGHLLifecycleClass];
#else
NSArray *stringArray = [[NSUserDefaults standardUserDefaults] objectForKey:kGHLLifecycleClass];
self.subCalsses = [self _classArrayWithStringArray:stringArray];
#endif
}
return self;
}
- (NSArray *)_classArrayWithStringArray:(NSArray *)stringArray {
NSMutableArray *classArray = [NSMutableArray new];
[stringArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
Class cls = NSClassFromString(obj);
if (cls) [classArray addObject:[cls new]];
}];
return [classArray copy];
}
- (NSArray *)_findAllSubClass:(Class)class {
// 注册类的总数
int count = objc_getClassList(NULL, 0);
NSMutableArray *array = [NSMutableArray new];
// 获取全部已注册的类
Class *classes = (Class *)malloc(sizeof(Class) * count);
objc_getClassList(classes, count);
for (int i = 0; i < count; i++) {
if (class == class_getSuperclass(classes[i])) {
[array addObject:[NSString stringWithFormat:@"%@", classes[i]]];
}
}
free(classes);
return array;
}
复制代码
消息转发过程:
- (BOOL)_containsProtocolMethod:(SEL)selector {
unsigned int outCount = 0;
struct objc_method_description *methodDesc = protocol_copyMethodDescriptionList(@protocol(UIApplicationDelegate), NO, YES, &outCount);
for (int idx = 0; idx < outCount; idx++) {
if (selector == methodDesc[idx].name) {
free(methodDesc);
return YES;
}
}
free(methodDesc);
return NO;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([self.realDelegate respondsToSelector:aSelector]) {
return YES;
}
for (GHLLifecycle *module in self.subClasses) {
if ([self _containsProtocolMethod:aSelector] && [module respondsToSelector:aSelector]) {
return YES;
}
}
return [super respondsToSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (![self _containsProtocolMethod:aSelector] && [self.realDelegate respondsToSelector:aSelector]) {
return self.realDelegate;
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
struct objc_method_description methodDesc = protocol_getMethodDescription(@protocol(UIApplicationDelegate), aSelector, NO, YES);
if (methodDesc.name == NULL && methodDesc.types == NULL) {
return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}
return [NSMethodSignature signatureWithObjCTypes:methodDesc.types];;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSMutableArray *allModules = [NSMutableArray arrayWithObjects:self.realDelegate, nil];
[allModules addObjectsFromArray:self.subClasses];
// BOOL 型返回值特殊处理
if (anInvocation.methodSignature.methodReturnType[0] == 'B') {
BOOL realReturnValue = NO;
for (GHLLifecycle *module in allModules) {
if ([module respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:module];
BOOL returnValue = NO;
[anInvocation getReturnValue:&returnValue];
realReturnValue = returnValue || realReturnValue;
}
}
[anInvocation setReturnValue:&realReturnValue];
} else {
for (GHLLifecycle *module in allModules) {
if ([module respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:module];
}
}
}
}
- (void)doNothing {
}
复制代码
无侵入、无注册,我的感受仍是比较完美的。虽然最后只是基于青木的方案作了免注册的优化,可是思考过程当中的其余方案也是值得分享的!
以上,就是总结的全部组件化分发生命周期的方案了。若是你还有其余更好方案,欢迎讨论! 全部的实践都在 Demo 里了: GHLShareLifecycle