重构臃肿的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的每一个职责定义一个命令,这个命令的名字有他们自己指定
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
我们的解决方案满足定义的标准
- 每个命令都有单一的职责
- 无需更改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)
}
}
他满足我们在开始时提出的所有要求
- 每个叶子类都有一个单一职责
- 很容易添加一个叶子类,而无需改变Appdelgate的内容
- 每个叶子类能够容易的被单独测试
方案3: 中介者设计模式
中介者模式封装了一系列对象的相互作用的方式,使得这些对象不必相互明显作用。从而使它们可以松散耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。
如果您想了解有关此模式的更多信息,我建议您查看Mediator Pattern Case Study。
让我们定义AppLifecycleMediator
将UIApplication
的生命周期通知底下的监听者,这些监听者必须遵循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仅仅需要初始化它一次,就能让他正常工作。
他满足我们在开始时提出的所有要求
- 每个监听者都有一个单一职责
- 很容易添加一个监听者,而无需改变Appdelgate的内容
- 每个监听者以及中介者能够容易的被单独测试
总结
我们一致认为大多数的AppDelegates都不合理,过于复杂,职责太多,我们称这样的类为Massive App Delegates。
通过使用一些软件的设计模式,Massive App Delegates可以被分割成多个类,每个类都有单一职责,这使得它们能够被单独的测试
这样的代码很容易更改,因为他不会在你的app中产生一连串的更改。它非常灵活,可以在将来提取或重用