ios

重构臃肿的APP Delegate(译)

2018-08-24  本文已影响0人  lkkwxy

原文地址
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里职责列表,我相信每个人都写过类似的代码或者碰巧正在维护一个类似的项目

像这样的臃肿的APPdelegate明显属于意大利面条式代码的定义,显然的,支持、扩展、测试这样的代码是非常困难和容易出错的,例如,查看 Telegram’s AppDelegate’s的源代码让我感到非常恐惧

让我们称为这样大的类叫做 Massive App Delegates,就像我们把类似的ViewController叫做Massive View Controller一样。

解决方案

在我们了解到Massive App Delegates的问题非常重要之后,让我们看看可能的解决方案。

每个方案必须满足一下标准

方案1: 命令设计模式

命令模式是描述对象被称作命令相当于是一个简单的方法或者事件。因为对象封装了触发自身所需的所有参数,因此命令的调用者不知道该命令做了什么以及响应者是谁

可以为APPdelegate的每一个职责定义一个命令,这个命令的名字有他们自己指定

protocol Command {
    func execute()
}

struct InitializeThirdPartiesCommand: Command {
    func execute() {
        // Third parties are initialized here
    }
}

struct InitialViewControllerCommand: Command {
    let keyWindow: UIWindow

    func execute() {
        // Pick root view controller here
        keyWindow.rootViewController = UIViewController()
    }
}

struct InitializeAppearanceCommand: Command {
    func execute() {
        // Setup UIAppearance
    }
}

struct RegisterToRemoteNotificationsCommand: Command {
    func execute() {
        // Register for remote notifications here
    }
}

然后我们定义StartupCommandsBuilder来封装如何创建命令的详细信息。APPdelegate调用这个builder去初始化命令并执行这些命令

// MARK: - Builder
final class StartupCommandsBuilder {
    private var window: UIWindow!

    func setKeyWindow(_ window: UIWindow) -> StartupCommandsBuilder {
        self.window = window
        return self
    }

    func build() -> [Command] {
        return [
            InitializeThirdPartiesCommand(),
            InitialViewControllerCommand(keyWindow: window),
            InitializeAppearanceCommand(),
            RegisterToRemoteNotificationsCommand()
        ]
    }
}

// MARK: - App Delegate
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

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

        return true
    }
}

如果APPdelegate需要添加新的职责,则可以创建新的命令,然后把命令添加到builder里去而无需去改变APPdelegate

我们的解决方案满足定义的标准

方案2: 组合设计模式

组合模式允许你讲对象组合成树形结构来表现‘整体/部分’层次结构,一个很明显的例子就是iOS里的UIView以及它的subviews

这个想法主要是有一个组装类和叶子类,每个叶子类负责一个职责,而组装类负责调用所有叶子类的方法


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) }
    }
}

接下来,实现执行具体职责的叶子类

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
    }
}

我们定义AppDelegateFactory来封装创建的逻辑,在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)
    }
}

他满足我们在开始时提出的所有要求

方案3: 中介者设计模式

中介者模式封装了一系列对象的相互作用的方式,使得这些对象不必相互明显作用。从而使它们可以松散耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。

如果您想了解有关此模式的更多信息,我建议您查看Mediator Pattern Case Study

让我们定义AppLifecycleMediatorUIApplication的生命周期通知底下的监听者,这些监听者必须遵循AppLifecycleListener协议,如果需要监听者要能扩展新的方法

// MARK: - AppLifecycleListener
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])
    }
}

现在它只需要1行代码就能加入到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仅仅需要初始化它一次,就能让他正常工作。

他满足我们在开始时提出的所有要求

总结

我们一致认为大多数的AppDelegates都不合理,过于复杂,职责太多,我们称这样的类为Massive App Delegates。

通过使用一些软件的设计模式,Massive App Delegates可以被分割成多个类,每个类都有单一职责,这使得它们能够被单独的测试

这样的代码很容易更改,因为他不会在你的app中产生一连串的更改。它非常灵活,可以在将来提取或重用

上一篇 下一篇

猜你喜欢

热点阅读