AppDelegate的模組化+瘦身

窗前有月光發表於2019-02-12

前言

關於iOS的模組化,要追溯到16年接觸的BeeHive了,BeeHive將功能模組化,以module的形式進行構建,以performSelector:的形式進行module的事件響應,以protocol的形式進行module間的通訊。可以說思路非常清晰明瞭了。關於BeeHive的程式碼傳送門alibaba/BeeHive,star已3.2k,關於BeeHive原始碼解析可參考霜神文章傳送門BeeHive —— 一個優雅但還在完善中的解耦框架。實際上我並不認為BeeHive可以真正用到我們專案中來,它確實構建了module,但是module例項帶來的記憶體問題會讓人頭疼。個人認為將BeeHive思想中的module部分改造一下用在我們的AppDelegate中是完全可行的。下面進入正文。

目錄

一、模組拆分

二、模組事件響應

三、模組管理

  • 1.模組註冊
  • 2.觸發event
  • 3.移除module

四、總結

一、模組拆分

畫了一個結構圖,module1到module4為我們需要在Appdelegate中進行處理的業務邏輯,比如說我們的資料庫處理分享功能推送功能等等。

AppDelegate的模組化+瘦身
首先為所有模組定義了三個介面:

@protocol SHRMAppEventModuleProtocol <UIApplicationDelegate>

- (NSInteger)moduleLevel;
- (void)destroyModule;
- (NSString *)moduleID;

@end
複製程式碼

介面定義了三個函式,moduleLevel返回module執行的優先順序,destroyModule用來對module進行釋放,moduleID返回當前module的id。介面的預設實現統一在BaseAppEventModule中進行。BaseAppEventModule為所有module的父類,只有繼承了BaseAppEventModule的module才能被管理。

關於BaseAppEventModule的預設實現也很簡單,對module進行銷燬的時候用到了SHRMAppEventModuleManager下面會講到,優先順序預設設定100.

@interface SHRMBaseAppEventModule : NSObject <SHRMAppEventModuleProtocol>

@end


#define MODULE_LEVEL_DEFAULT 100
@implementation SHRMBaseAppEventModule

- (NSInteger)moduleLevel {
    return MODULE_LEVEL_DEFAULT;
}

- (void)destroyModule {
    [[SHRMAppEventModuleManager sharedInstance] removeModule:[self moduleID]];
    NSLog(@"%@ destroy",NSStringFromClass([self class]));
}

- (NSString *)moduleID {
    return NSStringFromClass([self class]);
}

@end
複製程式碼

模組的建立上面說到了必須要繼承自SHRMBaseAppEventModule,只有繼承了SHRMBaseAppEventModule的module才會被管理,因為只有SHRMBaseAppEventModule遵循了SHRMAppEventModuleProtocol協議。關於module建立部分:

@interface testMudule : SHRMBaseAppEventModule
@end

@implementation testMudule

- (NSInteger)moduleLevel {
    return 1;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self initMudule];
    [self destroyModule];
    return YES;
}

- (void)initMudule {
    NSLog(@"testMudule init");
}

@end
複製程式碼

application: didFinishLaunchingWithOptions:函式的實現展示了一個module的整個生命週期,從建立到銷燬的過程,那麼application: didFinishLaunchingWithOptions:是怎麼響應的,實際上module的標頭檔案並沒有暴漏任何介面,到這裡就實現了功能的模組化。那為什麼還能執行到這裡,這要感謝強大的runtime函式performSelector:,下面講一下我對module事件響應的處理。

二、模組事件響應

還是以上面的application: didFinishLaunchingWithOptions:函式為例,它是怎麼來的,很明顯這是AppDelegate裡面的APP生命週期回撥:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [[SHRMAppEventModuleManager sharedInstance] handleApplicationEvent:@selector(application:didFinishLaunchingWithOptions:)
                                                              Complete:^(id  _Nonnull module, SEL  _Nonnull sel) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [module performSelector:sel
                     withObject:application
                     withObject:launchOptions];
#pragma clang diagnostic pop
    }];
    return YES;
}
複製程式碼

沒看錯,這樣一搞AppDelegateapplication: didFinishLaunchingWithOptions:就剩這些了,這樣一來,所有實現了application: didFinishLaunchingWithOptions:的modlue都會被呼叫,呼叫優先順序根據介面定義的moduleLevel返回值確定。到這裡我們就完成了AppDelegate瘦身的工作,實際上AppDelegate中的其他回撥處理是一樣的。當然還有一個很重要的沒有說,就是SHRMAppEventModuleManager做了什麼,通過上面的結構圖能夠看到SHRMAppEventModuleManager是個中介軟體,用來處理module與AppDelegate間的關係。下面說到第三部分,模組管理。

三、模組管理

SHRMAppEventModuleManager為一個單例,提供了三個介面:

/**
 初始化所有的AppDelegate相關的Event Modules
 */
- (void)registedAllModules;

/**
 觸發event module處理AppDelegate回撥事件
 
 @param eventSel AppDelegate 回撥事件訊息
 @param complete module處理handle
 */
- (void)handleApplicationEvent:(SEL)eventSel
                      Complete:(void(^)(id module,SEL sel))complete;

/**
 移除module物件
 
 @param moduleID module ID
 */
- (void)removeModule:(NSString *)moduleID;
複製程式碼

1.模組註冊

模組註冊的思路是完全按照BeeHive的思想來的,在編譯期就將我們的module通過__attribute函式進行註冊。在執行期再將我們註冊好的module取出來在registedAllModules中進行例項化,按照level的返回值進行排序儲存。__attribute函式具體做了什麼可以參考我之前的文章寫一個易於維護使用方便效能可靠的Hybrid框架(三)—— 配置外掛關於外掛註冊部分的解釋。

2.觸發event

handleApplicationEvent:Complete:為module事件響應的核心函式:

- (void)handleApplicationEvent:(SEL)eventSel
                      Complete:(void(^)(id module,SEL sel))complete {
   
    NSMutableArray *tmpAppEventModules = [[NSMutableArray alloc] initWithArray:self.appEventModules];
    for (id<SHRMAppEventModuleProtocol>module in tmpAppEventModules)
    {
        if ([module conformsToProtocol:@protocol(SHRMAppEventModuleProtocol)])
        {
            if ([module respondsToSelector:eventSel]) {
                if (complete) {
                    complete(module,eventSel);
                }
            }
        }
    }
}
複製程式碼

if ([module respondsToSelector:eventSel])就會執行completemodulesel返回,也就是到了AppDelegate裡面,繼而執行modulesel函式,並且將引數傳遞過去。

3.移除module

module的移除,在AppDelegate裡面,我們將程式啟動之後呼叫完就不再使用的功能module會手動執行移除操作。這也是前言所說的BeeHive在module移除這一塊會稍顯複雜,但是在AppDelegate裡面,我們是完全可以知道哪些module在載入之後可以立即移除的,另外我們僅在AppDelegate中進行模組化,產生的module例項也會非常少,so,完全沒必要擔心module所帶來的記憶體開銷問題。

- (void)removeModule:(NSString *)moduleID {
    NSInteger index = NSNotFound;
    NSInteger resIndex = 0;
    for (id<SHRMAppEventModuleProtocol>module in self.appEventModules)
    {
        if ([[module moduleID] isEqualToString:moduleID])
        {
            index = resIndex;
            break;
        }
        resIndex++;
    }
    
    if (index != NSNotFound) {
        [self.appEventModules removeObjectAtIndex:index];
    }
}
複製程式碼

總結

最後總結一下,關於模組化,現在總體來看比較流行,也有很多介紹模組化,元件化具體實施之路的文章,都很優秀,也值得學習。關於解耦,我更傾向於protocol的方式,介面protocol化,程式碼易讀且清晰。之前看過mrpeak在元件化方面的文章,傳送門iOS 元件化方案,個人覺得protocol+version的方案和BeeHive非常像,protocol解耦,version進行module的版本管理,但是還是沒有解決module所帶來的記憶體開銷問題,module一旦細化,何時銷燬也是讓開發者頭疼的問題。先說這麼多,各位小夥伴有任何問題歡迎評論區討論。

最後附上DEMO傳送門:AppDelegateMudule,歡迎star?。

相關文章