原文 : 與佳期的個人博客(gonghonglou.com)
我不要你覺得,我要我覺得,聽我的,組件化分發(fā)生命周期就這么寫!
是什么
組件化分發(fā)生命周期是什么?就是將主工程的生命周期分發(fā)到各個組件里去。直觀些的介紹則是:AppDelegate 遵循并實現了 UIApplicationDelegate 代理,其中包括 willFinishLaunchingWithOptions:、didFinishLaunchingWithOptions:、applicationWillEnterForeground:、applicationDidEnterBackground: 等等方法,包含了主工程的各個階段將執(zhí)行的方法,我們要做的就是在主工程的這些階段方法被執(zhí)行的時候,各個組件里相對應的階段方法同時會被執(zhí)行,這樣,主工程和各個組件便共享了生命周期,
為什么
至于為什么要將主工程的生命周期分發(fā)到各個組件中,原因有以下幾點:
1、替換 load 方法
因為 load 方法時機較早,所有很多時候會在 load 方法里執(zhí)行注冊,初始化等操作,但這也會導致 load 方法的濫用,將一些本可以靠后執(zhí)行的操作提前執(zhí)行了,可能引發(fā) APP 啟動耗時過長的問題,需要做 load 耗時監(jiān)測,治理起來困難,所以很多團隊是禁用 load 方法的。
將這些操作方法放到生命周期方法里去做顯然更好,尋找合理的時機執(zhí)行相應的操作,耗時能檢測功能也比較好做。
2、解決 AppDelegate 臃腫問題
工程中難免有一系列的注冊、初始化操作,比如:APP 性能檢測、bug 收集、打點等一系列工具的注冊;各種基礎組件涉及的初始化或重置操作。
將這些操作放到組件自己的生命周期方法里去執(zhí)行,避免了 AppDelegate 的臃腫,而且各基礎組件與主工程解耦,開發(fā)維護更方便。
3、Debugger 類組件可插拔
某些 Debugger 類組件在工作前可能需要注冊操作,將注冊操作放在 Pod 自己的生命周期里。這樣一來,對于 Debugger 類組件只需要在 Podfile 里控制加載形式,即可做到 Debug/Release 環(huán)境組件可插拔,如:
pod 'DebuggerKit', :configurations => ['Debug']
怎么做
相比于將 AppDelegate 里的所有階段方法分發(fā)出去,先介紹兩種相對輕量的做法,也能做到和分發(fā)生命周期類似的能力:
1、sunnyxx 的 Notification Once
巧妙的通知注冊
+ (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];
}];
}
很巧妙的方法,優(yōu)點很明顯:輕量!雖然侵入了 load 方法,不過如果沒有 load 的濫用的話也可以接受,畢竟只是在 load 里執(zhí)行了注冊行為,具體的執(zhí)行時機還是 UIApplicationDidFinishLaunchingNotification
缺點是該方法的響應是在 - application:didFinishLaunchingWithOptions: 調用完成后發(fā)送,時機沒法精確控制,因為有的時候因為時機問題,我們想讓各種 Pod 里的注冊操作在 AppDelegate 的 didFinishLaunchingWithOptions: 方法靠前執(zhí)行,即先執(zhí)行組件里的注冊操作再執(zhí)行 AppDelegate 里的操作。
參考原文:Notification Once
2、美團的 Kylin 注冊函數
在編譯時把數據(如函數指針)寫入到可執(zhí)行文件的 __DATA 段中,在運行時的某個階段(如 willFinishLaunch)再從 __DATA 段取出數據進行相應的操作(調用函數)。
Kylin實現原理簡述:Clang 提供了很多的編譯器函數,它們可以完成不同的功能。其中一種就是 section() 函數,section()函數提供了二進制段的讀寫能力,它可以將一些編譯期就可以確定的常量寫入數據段。 在具體的實現中,主要分為編譯期和運行時兩個部分。在編譯期,編譯器會將標記了 attribute((section())) 的數據寫到指定的數據段中,例如寫一個 {key(key代表不同的啟動階段), *pointer} 對到數據段。到運行時,在合適的時間節(jié)點,在根據key讀取出函數指針,完成函數的調用。
這種方案呢,最好去寫個專門的工具,如 Kylin,去實現 {key, *pointer} 對的注冊和調用操作。對于使用方來說,添加一種函數執(zhí)行時,使用 Kylin 注冊,并在 AppDelegate 的合理階段調起方法。
除了以上方法之外還有一些比較“大型”的做法就是把 AppDelegate 的生命周期完整的分發(fā)出去:
3、手動注冊、遍歷分發(fā)
這是我之前公司的做法,提前注冊 Lifecycle 類(可實現 AppDelegate 的各階段方法),在 AppDelegate 各階段方法執(zhí)行的同時遍歷 lifecycle 類執(zhí)行相應方法。具體的做法是,
1)項目中存在一份配置文件,文件里配置著各個 pod 的 Lifecycle 類名,該類里實現了 AppDelegate 的某幾個階段方法。
2)項目啟動的時候加載這份配置文件,根據類名反射成 Lifecycle 類,將所有的類添加到一個數組中(LifecycleArray)。
3)在 AppDelegate 和 UIResponder 的繼承中間加一個 MyAppDelegate 類(GHLAppDelegate : MyAppDelegate : UIResponder),該類擁有 AppDelegate 的所有方法,在每個階段的方法里遍歷 LifecycleArray 數組,調用各個 Lifecycle 類的本階段方法。
4)在 AppDelegate 的各階段方法里首先調用一下 super 方法。
這樣,在 AppDelegate 各階段執(zhí)行的時候就會執(zhí)行父類方法,遍歷所有 pod 里的 Lifecycle 類,執(zhí)行相應方法,從而實現生命周期的分發(fā)。

這種做法的優(yōu)點是沒什么騷操作(姑且算優(yōu)點吧),都是基本方法遍歷調用,就一個反射操作也算常用吧。弊端就顯而易見了:
1)需要注冊行為。每添加一個 pod,想要為該 Pod 配置生命周期管理類的話都要去配置文件里注冊一次。雖然項目穩(wěn)定下來后 pod 基本不會變動,但使用起來總歸不夠理想,而且因為配置文件的存在,這種中心化的寫法會導致代碼臃腫,閱讀維護困難。
2)侵入 AppDelegate 類。需要更改 AppDelegate 的父類,并且在 AppDelegate 的各階段里調用 super。
4、Category 覆蓋、追加方法??
因為上一種方案中存在的問題,所以我在想怎么做既可以不用注冊,又不用侵入 AppDelegate 呢?Category!我想到這種方案:
1)新建 Lifecycle 類,用于向相應的 pod(第三步提到的建過分類的 Pod) 分發(fā)生命周期。該類擁有 AppDelegate 的所有生命周期方法。
2)為了不侵入 AppDelegate,給 AppDelegate 添加分類(AppDelegate+Lifecycle),用于在相應階段調用 Lifecycle 相應方法。在該分類里重寫 AppDelegate 的各階段方法,在各個方法里分別調用 Lifecycle 類的對應方法(這里其實也可以在分類里 hook 本類的方法來實現,但是為了寫法方便 Demo 里的做法是使用了第四步提供的方法:遍歷方法列表找到最后一個方法執(zhí)行)。
3)對使用方來說,只需要在自己的 pod 里新建 Lifecycle 類的分類(如:Lifecycle + Home、Lifecycle + Deatil 等),復寫本類的方法,即 AppDelegate 的生命周期方法,這些 pod 里的這些分類的這些方法會被 Lifecycle 類全部執(zhí)行。
4)怎么全部執(zhí)行呢?多個分類會根據加載順序互相覆蓋方法,正常情況下只執(zhí)行最后加載的那個分類的方法,因為最后加載的分類的方法被最后加到方法列表里,消息發(fā)送過程中最先被找到。解決思路是:找到那些被覆蓋的方法去執(zhí)行對應的 IMP:
4.1)在 Lifecycle 本類里去遍歷本類的方法列表,為了避免無限循環(huán),除了本類的方法(即方法列表的最后一個),對其他的同名 SEL 都執(zhí)行對應 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)為了寫法方便和統(tǒng)一,這里給第二步也提供了執(zhí)行 AppDelegate 本類方法的實現。在 AppDelegate 的方法列表里尋找同名的的最后一個 SEL,執(zhí)行對應的 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 的生命周期方法,起到分發(fā)的效果。

優(yōu)點即是:
1)對使用方來說不必注冊,只需創(chuàng)建 Lifecycle 的分類
2)不侵入 AppDelegate 代碼
缺點是:
1)要手動在主工程創(chuàng)建 AppDelegate 分類。
2)所添加的分類里的同名方法不會被覆蓋,有反常識。
3)還有個缺點,也是為什么在標題上加一個 ?? 的原因,是因為給一個類添加多個 Category,并分別覆蓋本類方法時 Xcode 會提示 warning
Category is implementing a method which will also be implemented by its primary class
5、消息轉發(fā)
因為上一種方案中存在的問題,我在想還有什么更好的方案,只提供一個 Pod 組件,就可以完成所有操作的方案。然后找到了青木同學的 組件化之組件生命周期管理 這篇文章,實現方案在文章里已經講的很詳細了,思路就是:
1)新建 Module 類,提供注冊功能,并且可以設置優(yōu)先級。使用方在自己的 Pod 繼承該類創(chuàng)建自己的生命周期管理類,并且在 load 方法調用 Module 類的注冊方法。
2)新建 UIApplication 的分類:UIApplication (Module),hook 掉 setDelegate: 方法,將代理設置給自己創(chuàng)建的類:ApplicationDelegateProxy。同時對包含所有注冊類的數組根據優(yōu)先級進行排序。
3)ApplicationDelegateProxy 類里不會實現 AppDelegate 里的那些方法,所以當系統(tǒng)來調用這些方法的時候,因為找不到 SEL 會進入消息轉發(fā)過程:
3.1) -respondsToSelector::系統(tǒng)內部會調用這個方法。
判斷是否實現了對應的 UIApplicationDelegate 代理方法。重寫該方法結合 AppDelegate 以及所有注冊的 Module 判斷是否有相應實現。
3.2)-forwardingTargetForSelector:: -respondsToSelector: 返回 YES ,便進入消息轉發(fā)階段,消息轉發(fā)的第二步就是該方法。
判斷要轉發(fā)的方法是否為 UIApplicationDelegate 的代理方法,如果不是,并且 AppDelegate 能響應,把消息轉發(fā)給 AppDelegate 去處理。
3.3)-methodSignatureForSelector: 和 -forwardInvocation: :如果消息沒有發(fā)給 AppDelegate,由自己來處理,將會這執(zhí)行這些方法。
在這一步首先根據協(xié)議直接返回代理方法的簽名,然后在 -forwardInvocation: 方法中,按照優(yōu)先級,依次把消息轉發(fā)給注冊的模塊。
3.4)消息轉發(fā)中處理返回值為 BOOL 類型的情況。
這樣就通過消息轉發(fā)完成了生命周期的分發(fā)。已經是很不錯的實現了,對外部文件沒有侵入,唯一的缺點就是需要一個注冊操作,而且還是在 load 方法里。
6、最后的優(yōu)化
我在想有什么方法可以去掉這個注冊操作?如果我們讓所有組件里控制生命周期的類都繼承自 Lifecycle 類,那么我們通過獲取 Lifecycle 的所有子類就能夠完成注冊操作了。思路很簡單:通過 runtime 獲取所有注冊的類,遍歷這些類判斷其父類是否是 Lifecycle 類。因為注冊類的總數可能會非常大,為了避免性能問題,將這個方法的調用控制在 Debug 模式下執(zhí)行,將拿到的數組存儲在本地,這樣在 Release 環(huán)境下直接獲取緩存數據即可。

照著這樣的思路又實現了一份優(yōu)化過的代碼,收集注冊子類:
- (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;
}
消息轉發(fā)過程:
- (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 {
}
無侵入、無注冊,個人感覺還是比較完美的。雖然最后只是基于青木的方案做了免注冊的優(yōu)化,但是思考過程中的其他方案也是值得分享的!
以上,就是總結的所有組件化分發(fā)生命周期的方案了。如果你還有其他更好方案,歡迎討論!
所有的實踐都在 Demo 里了: GHLShareLifecycle
后記
- 小白出手,請多指教。如言有誤,還望斧正!
- 轉載請保留原文地址:http://gonghonglou.com/2019/08/29/pod-lifecycle/