0%

重构臃肿的APP Delegate(译)

原文地址
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

让我们定义AppLifecycleMediatorUIApplication的生命周期通知底下的监听者,这些监听者必须遵循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 中产生一连串的更改。它非常灵活,可以在将来提取或重用