組件化分發(fā)生命周期

原文 : 與佳期的個人博客(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 的合理階段調起方法。

參考:美團外賣iOS App冷啟動治理

除了以上方法之外還有一些比較“大型”的做法就是把 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ā)。


image

這種做法的優(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ā)的效果。


image

優(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)境下直接獲取緩存數據即可。


image

照著這樣的思路又實現了一份優(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

后記

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容