原文地址 APP Delegate 将你的 APP 和系统连接起来,通常被认为是一个 iOS 项目的核心。然而普遍的趋势是,它随着项目的发展而不断的变大,逐渐的增加新的特性和功能,开始这和那的调用并最终变成意大利面条式的代码
在 app delegate 里修改任何东西的代价都是昂贵的,因为它将会影响你的整个 APP。毫无疑问的,对于一个健康的 iOS 项目来说保持保持 APP Delegate 的简洁是至关重要的
在本文中,让我们来看一下如何使用不同的方法使 app delegate 能够简洁,可重用和可测试的
问题描述 app delegate 是你 app 的根对象,他能确保你的 app 能够正确的和系统和其他 app 进行正确的交互。app delegate 通常也会承担很多职责,这让它变得难以修改,扩展和测试
甚至Apple 也鼓励你让你的 AppDelegate 至少承担起三个职责 (1、启动你的 APP;2、管理应用的状态;3、响应一些通知和事件(例如推送相关,与其他 app 交互相关…))
通过调查几十个最受欢迎的开源 iOS APP ,我写了一个经常写在 APPdelegate 里职责列表,我相信每个人都写过类似的代码或者碰巧正在维护一个类似的项目
初始化第三方库
初始化 Core Data 并管理数据迁移
为单元测试和 UI 测试配置应用的状态
管理UserDefaults初始化首次加载的标志,保存和加载数据
处理闪屏页
管理推送,获取权限,存储 token,处理自定义事件,将通知广播到应用的其他地方
配置UIAppearance
管理 app 的角标数
管理后台任务
管理 UI 堆栈配置,初始化控制器,选择根控制器
播放音频
开启 debug 日志
管理设备的方向
遵循各种协议,尤其是第三方的协议
弹出一些提示框
…
像这样的臃肿的 APPdelegate 明显属于意大利面条式代码的定义,显然的,支持、扩展、测试这样的代码是非常困难和容易出错的,例如,查看 Telegram’s AppDelegate’s 的源代码让我感到非常恐惧
让我们称为这样大的类叫做 Massive App Delegates,就像我们把类似的 ViewController 叫做 Massive View Controller 一样。
解决方案 在我们了解到 Massive App Delegates 的问题非常重要之后,让我们看看可能的解决方案。
每个方案必须满足一下标准
方案 1: 命令设计模式 命令模式是描述对象被称作命令相当于是一个简单的方法或者事件。因为对象封装了触发自身所需的所有参数,因此命令的调用者不知道该命令做了什么以及响应者是谁
可以为 APPdelegate 的每一个职责定义一个命令,这个命令的名字有他们自己指定
1 protocol Command { 2 func execute() 3 } 4 5 struct InitializeThirdPartiesCommand: Command { 6 func execute() { 7 // Third parties are initialized here 8 } 9 } 10 11 struct InitialViewControllerCommand: Command { 12 let keyWindow: UIWindow 13 14 func execute() { 15 // Pick root view controller here 16 keyWindow.rootViewController = UIViewController() 17 } 18 } 19 20 struct InitializeAppearanceCommand: Command { 21 func execute() { 22 // Setup UIAppearance 23 } 24 } 25 26 struct RegisterToRemoteNotificationsCommand: Command { 27 func execute() { 28 // Register for remote notifications here 29 } 30 }
然后我们定义StartupCommandsBuilder来封装如何创建命令的详细信息。APPdelegate 调用这个 builder 去初始化命令并执行这些命令
1 // MARK: - Builder 2 final class StartupCommandsBuilder { 3 private var window: UIWindow! 4 5 func setKeyWindow(_ window: UIWindow) -> StartupCommandsBuilder { 6 self.window = window 7 return self 8 } 9 10 func build() -> [Command] { 11 return [ 12 InitializeThirdPartiesCommand(), 13 InitialViewControllerCommand(keyWindow: window), 14 InitializeAppearanceCommand(), 15 RegisterToRemoteNotificationsCommand() 16 ] 17 } 18 } 19 20 // MARK: - App Delegate 21 @UIApplicationMain 22 class AppDelegate: UIResponder, UIApplicationDelegate { 23 var window: UIWindow? 24 25 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 26 StartupCommandsBuilder() 27 .setKeyWindow(window!) 28 .build() 29 .forEach { $0.execute() } 30 31 return true 32 } 33 }
如果 APPdelegate 需要添加新的职责,则可以创建新的命令,然后把命令添加到 builder 里去而无需去改变 APPdelegate
我们的解决方案满足定义的标准
每个命令都有单一的职责
无需更改 APPdelegate 就可以很容易的添加新的命令
每个命令可以很容易的被单独测试
方案 2: 组合设计模式 组合模式允许你讲对象组合成树形结构来表现‘整体/部分’层次结构,一个很明显的例子就是 iOS 里的 UIView 以及它的 subviews
这个想法主要是有一个组装类和叶子类,每个叶子类负责一个职责,而组装类负责调用所有叶子类的方法
1 2 typealias AppDelegateType = UIResponder & UIApplicationDelegate 3 4 class CompositeAppDelegate: AppDelegateType { 5 private let appDelegates: [AppDelegateType] 6 7 init(appDelegates: [AppDelegateType]) { 8 self.appDelegates = appDelegates 9 } 10 11 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 12 appDelegates.forEach { _ = $0.application?(application, didFinishLaunchingWithOptions: launchOptions) } 13 return true 14 } 15 16 func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 17 appDelegates.forEach { _ = $0.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } 18 } 19 }
接下来,实现执行具体职责的叶子类
1 class PushNotificationsAppDelegate: AppDelegateType { 2 func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 3 // Registered successfully 4 } 5 } 6 7 class StartupConfiguratorAppDelegate: AppDelegateType { 8 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 9 // Perform startup configurations, e.g. build UI stack, setup UIApperance 10 return true 11 } 12 } 13 14 class ThirdPartiesConfiguratorAppDelegate: AppDelegateType { 15 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 16 // Setup third parties 17 return true 18 } 19 }
我们定义AppDelegateFactory来封装创建的逻辑,在 APPdelegate 通过工厂方法创建组装类,然后通过他去调用所有的方法
1 enum AppDelegateFactory { 2 static func makeDefault() -> AppDelegateType { 3 return CompositeAppDelegate(appDelegates: [PushNotificationsAppDelegate(), StartupConfiguratorAppDelegate(), ThirdPartiesConfiguratorAppDelegate()]) 4 } 5 } 6 7 @UIApplicationMain 8 class AppDelegate: UIResponder, UIApplicationDelegate { 9 10 var window: UIWindow? 11 let appDelegate = AppDelegateFactory.makeDefault() 12 13 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 14 _ = appDelegate.application?(application, didFinishLaunchingWithOptions: launchOptions) 15 return true 16 } 17 18 func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 19 appDelegate.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) 20 } 21 }
他满足我们在开始时提出的所有要求
每个叶子类都有一个单一职责
很容易添加一个叶子类,而无需改变 Appdelgate 的内容
每个叶子类能够容易的被单独测试
方案 3: 中介者设计模式 中介者模式封装了一系列对象的相互作用的方式,使得这些对象不必相互明显作用。从而使它们可以松散耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。
如果您想了解有关此模式的更多信息,我建议您查看Mediator Pattern Case Study 。
让我们定义AppLifecycleMediator将UIApplication的生命周期通知底下的监听者,这些监听者必须遵循AppLifecycleListener协议,如果需要监听者要能扩展新的方法
1 // MARK: - AppLifecycleListener 2 protocol AppLifecycleListener { 3 func onAppWillEnterForeground() 4 func onAppDidEnterBackground() 5 func onAppDidFinishLaunching() 6 } 7 8 // MARK: - Mediator 9 class AppLifecycleMediator: NSObject { 10 private let listeners: [AppLifecycleListener] 11 12 init(listeners: [AppLifecycleListener]) { 13 self.listeners = listeners 14 super.init() 15 subscribe() 16 } 17 18 deinit { 19 NotificationCenter.default.removeObserver(self) 20 } 21 22 private func subscribe() { 23 NotificationCenter.default.addObserver(self, selector: #selector(onAppWillEnterForeground), name: .UIApplicationWillEnterForeground, object: nil) 24 NotificationCenter.default.addObserver(self, selector: #selector(onAppDidEnterBackground), name: .UIApplicationDidEnterBackground, object: nil) 25 NotificationCenter.default.addObserver(self, selector: #selector(onAppDidFinishLaunching), name: .UIApplicationDidFinishLaunching, object: nil) 26 } 27 28 @objc private func onAppWillEnterForeground() { 29 listeners.forEach { $0.onAppWillEnterForeground() } 30 } 31 32 @objc private func onAppDidEnterBackground() { 33 listeners.forEach { $0.onAppDidEnterBackground() } 34 } 35 36 @objc private func onAppDidFinishLaunching() { 37 listeners.forEach { $0.onAppDidFinishLaunching() } 38 } 39 } 40 41 extension AppLifecycleMediator { 42 static func makeDefaultMediator() -> AppLifecycleMediator { 43 let listener1 = ... 44 let listener2 = ... 45 return AppLifecycleMediator(listeners: [listener1, listener2]) 46 } 47 }
现在它只需要 1 行代码就能加入到 AppDelegate 中
1 @UIApplicationMain 2 class AppDelegate: UIResponder, UIApplicationDelegate { 3 var window: UIWindow? 4 5 let mediator = AppLifecycleMediator.makeDefaultMediator() 6 7 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 8 return true 9 } 10 }
这个中介者自动订阅了所有的事件。AppDelegate 仅仅需要初始化它一次,就能让他正常工作。
他满足我们在开始时提出的所有要求
每个监听者都有一个单一职责
很容易添加一个监听者,而无需改变 Appdelgate 的内容
每个监听者以及中介者能够容易的被单独测试
总结 我们一致认为大多数的 AppDelegates 都不合理,过于复杂,职责太多,我们称这样的类为 Massive App Delegates。
通过使用一些软件的设计模式,Massive App Delegates 可以被分割成多个类,每个类都有单一职责,这使得它们能够被单独的测试
这样的代码很容易更改,因为他不会在你的 app 中产生一连串的更改。它非常灵活,可以在将来提取或重用