iOS 重構AppDelegate


一、Massive AppDelegate

AppDelegate 是應用程序的根對象,它連接應用程序和系統,確保應用程序與系統以及其他應用程序正確的交互,通常被認為是每個 iOS 項目的核心。

隨着開發的迭代升級,不斷增加新的功能和業務,它的代碼量也不斷增長,最終導致了 Massive AppDelegate。

在復雜 AppDelegate 里修改任何東西的成本都是很高的,因為它將會影響你的整個 APP,一不留神產生 bug。毫無疑問,保持 AppDelegate 的簡潔和清晰對於健康的 iOS 架構來說是至關重要的。本文將使用多種方法來重構,使之簡潔、可重用和可測。

AppDelegate 常見的業務代碼如下:

  • 日志埋點統計數據分析
  • 初始化數據存儲系統
  • 配置 UIAppearance
  • 管理 App Badge 數字
  • 管理通知:請求權限,存儲令牌,處理自定義操作,將通知傳播到應用程序的其余部分
  • 管理 UI 堆棧配置:選擇初始視圖控制器,執行根視圖控制器轉換
  • 管理 UserDefaults:設置首先啟動標志,保存和加載數據
  • 管理后台任務
  • 管理設備方向
  • 更新位置信息
  • 初始化第三方庫(如分享、日志、第三方登陸、支付)

這些臃腫的代碼是反模式的,導致難於維護,顯然支持擴展和測試這樣的類非常復雜且容易出錯。Massive AppDelegates 與我們經常談的 Massive ViewController 的症狀非常類似。

看看以下可能的解決方案,每個 Recipe(方案)遵循單一職責、易於擴展、易於測試原則

二、命令模式 Command Design Pattern

命令模式是一種數據驅動的設計模式,屬於行為型模式。

請求以命令的形式包裹在對象中,並傳給調用對象。調用對象尋找可以處理該命令的合適的對象,並把該命令傳給相應的對象,該對象執行命令。因此命令的調用者無需關心命令做了什么以及響應者是誰。

可以為 AppDelegate 的每一個職責定義一個命令,這個命令的名字自行指定。

/// 命令協議
@protocol Command <NSObject>
- (void)execute;
@end

/// 初始化第三方庫
@interface InitializeThirdPartiesCommand : NSObject <Command>

@end

/// 初始化主視圖
@interface InitializeRootViewControllerCommand : NSObject <Command>
@property (nonatomic, strong) UIWindow * keyWindow;
@end

/// 初始化視圖全局配置
@interface InitializeAppearanceCommand : NSObject <Command>

@end

/// ...

然后定義一個統一調用的類 StartupCommandsBuilder 來封裝如何創建命令的詳細信息。AppDelegate 調用這個 builder 去初始化命令並執行這些命令。

@implementation StartupCommandsBuilder

// 返回數組,元素為遵守 Command 協議的對象
- (NSArray<id<Command>> *)build
{
    return @[ [InitializeAppearanceCommand new], 
              [InitializeRootViewControllerCommand new], 
              [InitializeThirdPartiesCommand new] ];
}

@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{    
    [[[[StartupCommandsBuilder alloc] init] build] enumerateObjectsUsingBlock:^(id<Command> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [obj execute];
    }];
    
    return YES;
}

如果 AppDelegate 需要添加新的職責,則可以創建新的命令,然后把命令添加到 Builder 里而無需去改變 AppDelegate。解決方案滿足單一職責、易於擴展、易於測試原則。

三、組合設計模式 Composite Design Pattern

組合模式又叫部分整體模式,用於把一組相似的對象當作一個單一的對象。

組合模式依據樹形結構來組合對象,用來表示部分以及整體層次。這種類型的設計模式屬於結構型模式,它創建了對象組的樹形結構。一個很明顯的例子就是 iOS 里的 UIView 以及它的 subviews。

這個想法主要是有一個組裝類和葉子類,每個葉子類負責一個職責,而組裝類負責調用所有葉子類的方法。

/// 組裝類
@interface CompositeAppDelegate : UIResponder <UIApplicationDelegate>
+ (instancetype)makeDefault;
@end

@implementation CompositeAppDelegate

+ (instancetype)makeDefault
{
    // 這里要實現單例
    return [[CompositeAppDelegate alloc] init];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[PushNotificationAppDelegate new] application:application didFinishLaunchingWithOptions:launchOptions];
    [[ThirdPartiesConfiguratorAppDelegate new] application:application didFinishLaunchingWithOptions:launchOptions];

    return YES;
}

@end

實現執行具體職責的葉子類。

/// 葉子類。推送消息處理
@interface PushNotificationAppDelegate : UIResponder <UIApplicationDelegate>

@end

/// 葉子類。初始化第三方庫
@interface ThirdPartiesConfiguratorAppDelegate : UIResponder <UIApplicationDelegate>

@end


@implementation PushNotificationAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"PushNotificationAppDelegate");
    
    return YES;
}

@end



@implementation ThirdPartiesConfiguratorAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"ThirdPartiesConfiguratorAppDelegate");
    
    return YES;
}

@end

在 AppDelegate 通過工廠方法創建組裝類,然后通過它去調用所有的方法

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[CompositeAppDelegate makeDefault] application:application didFinishLaunchingWithOptions:launchOptions];
    
    return YES;
}

它滿足我們在開始時提出的所有要求,如果要添加一個新的功能,很容易添加一個葉子類,無需改變 AppDelegate,解決方案滿足單一職責、易於擴展、易於測試原則。

四、中介者模式 Mediator Design Pattern

中介者模式是用來降低多個對象和類之間的通信復雜性。

這種模式提供了一個中介類,該類通常處理不同類之間的通信,並支持松耦合,使代碼易於維護。中介者模式屬於行為型模式

如果想了解有關此模式的更多信息,建議查看 Mediator Pattern Case Study。或者閱讀文末給出關於設計模式比較經典的書籍。

讓我們定義 AppLifecycleMediator 將 UIApplication 的生命周期通知底下的監聽者,這些監聽者必須遵循AppLifecycleListener 協議,如果需要監聽者要能擴展新的方法。

@interface APPLifeCycleMediator : NSObject

+ (instancetype)makeDefaultMediator;

@end


@implementation APPLifeCycleMediator
{
    @private
        NSArray<id<AppLifeCycleListener>> * _listeners;
}
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (instancetype)initWithListeners:(NSArray<id<AppLifeCycleListener>> *)listeners
{
    if (self = [super init]) {
        
        _listeners = listeners;
        
        // 通知
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(onAppWillEnterForeground)
                                                     name:UIApplicationWillEnterForegroundNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(onAppDidEnterBackgroud)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(onAppDidFinishLaunching)
                                                     name:UIApplicationDidFinishLaunchingNotification
                                                   object:nil];
    }
    
    return self;
}

/// 定義好靜態類方法,初始化所有監聽者
+ (instancetype)makeDefaultMediator
{
    static APPLifeCycleMediator * mediator;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mediator = [[APPLifeCycleMediator alloc] initWithListeners:@[[VideoListener new], [SocketListener new]]];
    });
    return mediator;
}

- (void)onAppWillEnterForeground
{
    [_listeners[1] onAppWillEnterForeground];
}

- (void)onAppDidEnterBackgroud
{
    [_listeners[0] onAppDidEnterBackgroud];
}

- (void)onAppDidFinishLaunching
{

}

@end

定義 AppLifecycleListener 協議,以及協議的的實現者。

/// 監聽協議
@protocol AppLifeCycleListener <NSObject>
@optional
- (void)onAppWillEnterForeground;
- (void)onAppDidEnterBackgroud;
- (void)onAppDidFinishLaunching;

@end

@interface VideoListener : NSObject <AppLifeCycleListener>

@end


@interface SocketListener : NSObject <AppLifeCycleListener>

@end


@implementation VideoListener

- (void)onAppDidEnterBackgroud
{
    NSLog(@"停止視頻播放");
}

@end

@implementation SocketListener

- (void)onAppWillEnterForeground
{
    NSLog(@"開啟長鏈接");
}

@end

加入到 AppDelegate 中

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [APPLifeCycleMediator makeDefaultMediator];
    
    return YES;
}

這個中介者自動訂閱了所有的事件。AppDelegate 僅僅需要初始化它一次,就能讓它正常工作。每個監聽者都有一個單一職責,很容易添加一個監聽者,而無需改變 Appdelgate 的內容,每個監聽者以及中介者能夠容易的被單獨測試。

五、總結

大多數 AppDelegates 的設計都不太合理,過於復雜並且職責過多。我們稱這樣的類為 Massive App Delegates。

通過應用軟件設計模式,Massive App Delegate 可以分成幾個單獨的類,每個類都有單一的責任,可以單獨測試。

這樣的代碼很容易更改維護,因為它不會在您的應用程序中產生一連串的更改。它非常靈活,可以在將來提取和重用。

六、學習文章

最佳實踐:重構AppDelegate

Refactoring Massive App Delegate

iOSTips

OC設計模式:《Objective-C 編程之道:iOS 設計模式解析》

 Swift 設計模式:《Design_Patterns_by_Tutorials_v0.9.1

重構:《重構改善既有代碼的設計》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM