關於AppDelegate瘦身的多種解決方案

蘇大盒子發表於2018-06-19

在iOS專案的開發中,AppDelegate是一個耦合發生的重災地,很多專案的開發時間一長,AppDelegate就不可避免地出現,程式碼臃腫,呼叫順序混亂,邏輯複雜的問題。這個UIApplication的委託類,作為一個常駐記憶體的單例,它承載了太多太多的功能,連蘋果的官方文件都建議應該由AppDelegate來處理這些工作:

  • 1.app的啟動程式碼;
  • 2.響應app的狀態,比如app切換到後臺和前臺等狀態;
  • 3.響應外部傳遞給app的通知,比如說push,low-memory warnings;
  • 4.決定了app的狀態是否應該儲存或者恢復;
  • 5.響應不是傳送給特定view或者vc,而是傳送給app本身的事件;
  • 6.用來儲存一些不屬於特定vc的資料。

不得不吐槽一句,有時候,蘋果的官方文件的建議也不那麼靠譜啊?‍♀️。

一個業務邏輯稍複雜點的專案,上述6點的所有功能的程式碼直接一股腦塞到一個檔案裡,能不臃腫才怪了。

這裡介紹三種給AppDelegate瘦身的方式:

NSNotification

我們知道一個app的各種事件發生時除了會呼叫UIApplicationDelegate中的方法,同時還會傳送一個NSNotification,蘋果在UIApplication.h中宣告瞭這些通知。但是並不是所有方法都有對應的通知,這時我們可以仿照蘋果的命名規範補上未定義的這些方法對應的通知,然後在自己的AppDelegate中顯式地傳送它們。

比如,定義application:didRegisterUserNotificationSettings:方法對應的通知:

UIKIT_EXTERN NSNotificationName const UIApplicationDidRegisterUserNotificationSettingsNotification;
複製程式碼

同時別忘了定義引數對應的key:

UIKIT_EXTERN NSString *const UIApplicationUserNotificationSettingsKey;
複製程式碼

然後在AppDelegate中的application:didRegisterUserNotificationSettings:方法裡執行:

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
    [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationUserNotificationSettingsKey object:[UIApplication sharedApplication] userInfo:@{UIApplicationUserNotificationSettingsKey : notificationSettings}];
}
複製程式碼

最後在需要響應這個事件的模組中註冊通知,處理對應的業務邏輯即可。

ModuleManager

通過實現ModuleManager類,來管理專案中的模組,首先在軟體啟動時通過讀取配置檔案(通常用plist)讀取模組,在AppDelegate的每個事件接收到響應的時,在對應方法中逐一呼叫已註冊的模組對應的響應方法: 首先在啟動時load modules

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[ModuleManager sharedInstance] loadModulesFromPlist:[[NSBundle mainBundle] pathForResource:@"modules" ofType:@"plist"]];
}
複製程式碼

UIApplication event發生時,依次呼叫每個module的對應方法:

- (void)applicationDidBecomeActive:(UIApplication *)application {
    NSArray<id<ModuleProtocol>> *modules = [ModuleManager sharedInstance].modules;
    [modules enumerateObjectsUsingBlock:^(id<ModuleProtocol>  _Nonnull module, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([module respondsToSelector:_cmd]) {
            [module applicationDidBecomeActive:application];
        }
    }];
}
複製程式碼

ModuleProtocol繼承自UIApplicationDelegate,定義如下:

@protocol ModuleProtocol <UIApplicationDelegate>

//在這裡根據自己專案的業務定義模組的共有方法

@end
複製程式碼

AOP

AOP在Objective-C中利用method swizzle來實現,例子就不舉了,相信有一定經驗的同學應該都知道到底要如何實現。

對比

以上三種方法,從結果上來說都可以達成給AppDelegate瘦身的目的,但是各有優缺點,因此也會用在不同場景,我說一下自己的個人看法,拋磚引玉:

NSNotification

  • 使用NSNotification的好處在於,如果不需要響應系統未定義的通知,那麼你的AppDelegate裡甚至可以一行程式碼都不用寫,瘦身瘦成一道閃電!
  • 你無法管理通知的註冊者的呼叫順序,比如說你建立rootwindow的程式碼如果還依賴讀取配置模組,但是分別使用觀察通知將實現程式碼寫在了不同模組中,這時候就很難保證讀取配置模組在建立rootwindow之前執行。因此為了保證呼叫順序,可以保留部分程式碼在AppDelegate中。

ModuleManager

  • ModuleManager更適合高度元件化/模組化的專案,專案到了這個階段,工程中的任何功能都被封裝成了模組,因此可以通過調整plist檔案中的模組順序,來保證模組的執行順序是正確的。
  • 相對的,使用ModuleManager的設計,對工程程式碼的質量要求也會比較高,如果你的專案還未開始進行元件化/模組化的設計,使用這種方式來給AppDelegate瘦身的難度也會很大,此時選用NSNotification是更好的解決方案。

AOP

  • 使用AOP的需要慎重和小心,這一點在很多的技術部落格和書籍中都會有提到,AOP用的好會是一把架構的利劍,幫你披荊斬棘,但是用不好的話,也會傷到自己。
  • AOP同樣很不好控制程式碼的執行順序。
  • 利用AOP由於可以做到百分百的無侵入,因此很適合在做第三方SDK專案時使用,這樣能夠儘可能減少SDK的使用者的接入成本。
  • AOP可以結合NSNotification或ModuleManager使用,然後者達到完全免侵入(僅僅適合極端的程式碼潔癖主義者)。

以上三種方法,並沒有真正意義上的孰優孰劣,而是需要根據自己專案的特點來選擇更適合的方案。 最近在重構專案時,我結合AOP和NSNotification寫了一個小功能(https://github.com/jiaopen/AppDelegateExtensions),幾乎無侵入地解決AppDelegate臃腫問題。

Features:

  • AOP沒有使用category來實現methodswizzle,因為不是所有工程的AppDelegate起名相同,因此需要在load中顯式呼叫一行註冊程式碼。
  • 增加了常用的UIApplicationDelegate方法對應的通知,可以根據自已業務的情況補充。

歡迎糾錯,拍磚。

相關文章