一、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 可以分成幾個單獨的類,每個類都有單一的責任,可以單獨測試。
這樣的代碼很容易更改維護,因為它不會在您的應用程序中產生一連串的更改。它非常靈活,可以在將來提取和重用。
六、學習文章
Refactoring Massive App Delegate
OC設計模式:《Objective-C 編程之道:iOS 設計模式解析》
Swift 設計模式:《Design_Patterns_by_Tutorials_v0.9.1》
重構:《重構改善既有代碼的設計》