重构Massive AppDelegate

2018/8/21 posted in  iOS

本文是翻译自墙外的一片文章。文章通过分析iOS项目中 AppDelegate 中常负担的职能,并将其分为三类,通过引入不同的设计模式将代码进行抽离整合,最终达到单一职能、可重用、易测试的特点。
译者也在文章的影响下,对当前进行的项目进行了相应的兼容和实现,最终效果还是很不错的。

开始

AppDetelgate 连接了你的 App 与系统。通常它都是整个 iOS 项目的核心部分。通常,伴随着不断的开发,新特性与功能的增加,AppDelegate 最终变成很零碎的东西。

通常把所有的代码都加入 AppDelegate 是十分浪费的,因为它容易影响整个 App。因此,保持这个类的干净与整洁对于一个健壮的项目是非常重要的。
本文探讨了让AppDelegate变得简洁、可测、可重用的几点建议。

问题

AppDelegate 是整个 App 最根本的地方。负责与系统及其它的 App 直接打交道。这使得AppDelegate有非常多的任务, 并且很难进行修改以及扩展。

苹果官方支持至少要在AppDelegate中行使3种职能。

通过调查大量的开源的App,我总结了通常AppDelegate会行使的职能:
1. 初始化大量的第三方库
2. 初始化 Core Data stack 以及管理迁移
3. 配置 App 的 State,用于设置单元测试以及UI测试
4. 管理推送,本地推送或者远程推送
5. 配置 UIAppearance
6. 管理 UserDataManager:配置初次登录时的 Flag,保存以及加载数据。
7. 配置 App 的角标
8. 管理后台进程
9. 管理 UI 调用栈的配置。选择初始的 ViewController
10.播放声音
11. 关系分析文件
12. 输出 DebugLog
13. 控制转向问题
14. 实现各种代理协议,尤其是第三方的
15. 弹出 Alert

解决方案

既然我们承认上面这些问题的存在并且这也很重要,那么看一下我们要遵守那些原则

1.维持单一职责原则
2.易扩展
3.易测试

命令模式

对于每一种AppDelegate的职能,我们创建一个单独的执行类:

protocol Command {
    func execute()
}

struct RegisterCommand: Command {
    func execute()
}

struct SetAppearanceCommand: Command {
    ...
}

struct InitialViewControllerCommand: Command {
 ...
} 

然后创建一个统一的管理类进行管理,管理上面这些command的创建以及执行

final class StartupCommandsBuilder {
    private var window: UIWindow!
    
    func setKeyWindow(_ window: UIWindow)-> StartupCommandsBuilder {
        self.window = window
        return self
    }
    
    func build()-> [Command] {
        return [RegisterCommand(),
                SetAppearanceCommand(),
                InitialViewControllerCommand(window)
                ...]
    }
}

最终在delegate进行配置的时候,

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        StartupCommandsBuilder()
            .setKeyWindow(window!)
            .build()
            .forEach { $0.execute() }
        return true
    }

这样将 AppDelegate 主动调用的工作进行了清晰的切割,并且实现单一职能原则。大大减少了工作工作量

这样做实现了以下优点:
1. 每个 Command 都实现了1个功能
2. 不改动 AppDelegate 代码就可以直接进行添加
3. 每个命令都可以很简单的被单独检测。

组合模式

组合模式使得不同等级的对象可以看成一个实例。
这里主要处理 AppDelegate 中的代理工作。
对于所有要调用的代理,分别创建单独的代理模式,最终集中进行处理。

typealias AppDelegateType = UIResponder & UIApplicationDelegate

class CompositeAppDelegate: AppDelegateType {
    private let appDelegates: [AppDelegateType]

    init(appDelegates: [AppDelegateType]) {
        self.appDelegates = appDelegates
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        appDelegates.forEach { _ = $0.application?(application, didFinishLaunchingWithOptions: launchOptions) }
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        appDelegates.forEach { _ = $0.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) }
    }
}

然后实际工作的时候,需要实现的 AppDelegate 方法

//最终每个delegate的实现方法
class PushNotificationsAppDelegate: AppDelegateType {
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        // Registered successfully
    }
}

class StartupConfiguratorAppDelegate: AppDelegateType {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Perform startup configurations, e.g. build UI stack, setup UIApperance
        return true
    }
}

class ThirdPartiesConfiguratorAppDelegate: AppDelegateType {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Setup third parties
        return true
    }
}

最终,我们通过一个工厂模式建立创建的逻辑。
AppDelegate 创建了组合代理,并把它们给所有适合的地方调用。

enum AppDelegateFactory {
    static func makeDefault() -> AppDelegateType {
        return CompositeAppDelegate(appDelegates: [PushNotificationsAppDelegate(), StartupConfiguratorAppDelegate(), ThirdPartiesConfiguratorAppDelegate()])
    }
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let appDelegate = AppDelegateFactory.makeDefault()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        _ = appDelegate.application?(application, didFinishLaunchingWithOptions: launchOptions)
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        appDelegate.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
    }
}

这样最终达到的效果是
1. 每一个子delegate都只行使同一个职能。
2. 添加新的任务以及delegate很容易, 这并不需要修改主体代码
3. 所有的代理都很容易进行隔离测试

中介者模式

中介对象 通过一个非常隐蔽非直观的形式实现交互。

现在通过实现一个 AppLifecycleMediator 来传播 UIApplication 的生命周期事件给各个观察者。这些观察者必须遵守 AppLifecycleListener 协议。

// MARK: - AppLifecycleListener  App的生命周期事件
protocol AppLifecycleListener {
    func onAppWillEnterForeground()
    func onAppDidEnterBackground()
    func onAppDidFinishLaunching()
}

// MARK: - Mediator
class AppLifecycleMediator: NSObject {
    private let listeners: [AppLifecycleListener]

    init(listeners: [AppLifecycleListener]) {
        self.listeners = listeners
        super.init()
        subscribe()
    }

    deinit {
        NotificationCenter.default.removeObserver(self) //在销毁时取消监听
    }

    private func subscribe() {
        NotificationCenter.default.addObserver(self, selector: #selector(onAppWillEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(onAppDidEnterBackground), name: .UIApplicationDidEnterBackground, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(onAppDidFinishLaunching), name: .UIApplicationDidFinishLaunching, object: nil)
    }

    @objc private func onAppWillEnterForeground() {
        listeners.forEach { $0.onAppWillEnterForeground() }
    }

    @objc private func onAppDidEnterBackground() {
        listeners.forEach { $0.onAppDidEnterBackground() }
    }

    @objc private func onAppDidFinishLaunching() {
        listeners.forEach { $0.onAppDidFinishLaunching() }
    }
}

extension AppLifecycleMediator {
    static func makeDefaultMediator() -> AppLifecycleMediator {
        let listener1 = ...
        let listener2 = ...
        return AppLifecycleMediator(listeners: [listener1, listener2])
    }
}

通过一行代码完成在AppDelegate中的添加

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    let mediator = AppLifecycleMediator.makeDefaultMediator()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        return true
    }
}

中介对象自动的注册了对于所有事件的观察。在AppDelegate只要初始化一次,就能使其正常的工作
最终满足了文章开头的要求:
1. 每个监听器都只有一个功能
2. 不修改 AppDelegate的代码,很轻易的添加新的观察者/监听器
3. 每个观察者,又叫做协调器,可以很容易的做隔离测试。

总结

我们都承认大部分的 AppDelegate 都非理性的大,过度复杂,行使了过多的职能。我们把这些叫做Massive App Delegates
通过接入这些设计模式,Massive App Delegate 可以被分割成多个类,每个类实现单一职能,这样也易于隔离测试。
这样的代码非常易于修改,不会因为改动造成大量的连带反应。最终非常的领过,易于被抽离以及重用。

原文地址