原文地址
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 中产生一连串的更改。它非常灵活,可以在将来提取或重用