AppDelegate设计重构

476 阅读7分钟

目的:减轻appdelegate压力,可复用,可测试。保持清晰且统一的代码风格。

一、最初的代码

最开始我们的appdelegate可能是这样的

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // 1: 初始化window 指定根控制器
        registerWindow()
        
        // 2: 配置环境
        registerConfig()
        
        // 3: 注册Log
        registerLog()
        
        // ...
        return true
}
func registerWindow() {
    let vm = RootViewModel()
    let vc = RootViewController(viewModel: vm)
    let nav = UINavigationController(rootViewController: vc)
    window = UIWindow(frame: UIScreen.main.bounds)
    window?.makeKeyAndVisible()
    window?.backgroundColor = .white
    window?.rootViewController = nav
}
    
func registerConfig() {}
    
func registerLog() {}

或是用扩展整理

// MARK: 初始化window 指定根控制器
extension AppDelegate {
    func registerWindow() {
        let vm = RootViewModel()
        let vc = RootViewController(viewModel: vm)
        let nav = UINavigationController(rootViewController: vc)
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.makeKeyAndVisible()
        window?.backgroundColor = .white
        window?.rootViewController = nav
    }
}
​
// MARK - 注册Log
extension AppDelegate {
    func registerConfig() {}
}
​
// MARK - OpenUrlColleague
extension AppDelegate {
    func registerLog() {}
}

但是上述的方式仅仅只是把代码换了一个地方而已,换汤不换药,其本质还是没有改变,也不能说是错。开发者也能够保证对appdelegate代码清洁程度的控制,但既然已经走上了重构这条路,何不来的更彻底一点。

二、一点改变

在翻阅了一些文章后,我看到很多人使用这一种方式。虽然与上面的同属于从扩展做代码清洁的理解方式,但细看就会发现,其实已经淡化“扩展+函数式”的优化,渐入到以功能要素为优先考虑的思维方式,前一种是我们被功能牵着走,后一种则是使用功能点在代码中调度的具体表现

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // 1:
        window_application(application, didFinishLaunchingWithOptions: launchOptions)
        
        // 2:
        config_application(application, didFinishLaunchingWithOptions: launchOptions)
        
        // 3:
        log_application(application, didFinishLaunchingWithOptions: launchOptions)
        
        // 4:
        register_application(application, didFinishLaunchingWithOptions: launchOptions)
        
        // ...
        return true
    }
​
  func applicationWillTerminate(_ application: UIApplication) {
      // 4:
      register_applicationWillTerminate(application)
  }
​
// MARK: 初始化window 指定根控制器
extension AppDelegate {
    func window_application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
        let vm = RootViewModel()
        let vc = RootViewController(viewModel: vm)
        let nav = UINavigationController(rootViewController: vc)
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.makeKeyAndVisible()
        window?.backgroundColor = .white
        window?.rootViewController = nav
    }
}
​
// MARK - Config
extension AppDelegate {
    func config_application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {}
}
​
// MARK - Log
extension AppDelegate {
    func log_application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {}
}
​
// MARK - Register
extension AppDelegate {
    func register_application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {}
    
    func register_applicationWillTerminate(_ application: UIApplication) {}
}

三、一探究竟

资源点

一个中大型项目appdelegate内部可能会占用的资源点: * UIApplicationDelegate * 工程和网络环境配置 * 管理类的初始化、第三方库注册 * 更新信息 * window窗口 * rootVC的指定和切换 * UserDefaults加载数据 * 应用后台管理 * Url跳转 * 后台进程 * 结束进程 * 热启动加载 * 通知服务 更多..

中介者模式。(路由器模式)

AppDelegate

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
​
      AppDelegateMediator.defaultMediator.application(application, didFinishLaunchingWithOptions: launchOptions)
​
      return true
}

AppDelegateMediator

@objc protocol LifeAppDelegate {
    @objc optional func onAppDidFinishLaunching(options: [UIApplication.LaunchOptionsKey: Any]?)
}
​
class AppDelegateMediator: NSObject {
  
    static let defaultMediator = AppDelegateMediator([WindowColleague(), RegisterColleague()])
​
    let notification = NotificationCenter.default
​
    var mediators: [LifeAppDelegate]?
​
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
​
    convenience init(_ array: [LifeAppDelegate]) {
        self.init()
        mediators = array
    }
​
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
        _ = mediators?.map({ (mediator) in
            mediator.onAppDidFinishLaunching?(options: launchOptions)
        })
  }
​
}

AppDelegateColleague

class WindowColleague: LifeAppDelegate {
​
    var window: UIWindow?
    
    func onAppDidFinishLaunching(options: [UIApplication.LaunchOptionsKey : Any]?) {
        let vm = RootViewModel()
        let vc = RootViewController(viewModel: vm)
        let nav = UINavigationController(rootViewController: vc)
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.makeKeyAndVisible()
        window?.backgroundColor = .white
        window?.rootViewController = nav
    }
    
}
​
class RegisterColleague: LifeAppDelegate {
​
    func onAppDidFinishLaunching(options: [UIApplication.LaunchOptionsKey : Any]?) {}
    
    func onAppDidEnterBackground() {}
    
}

中介者自动订阅了所有事件。每个监听者都有单独的职责,我们通过增加监听者给appdelegate加入新的资源点,而是用中介者统一管理职能,同时高聚合低耦合,降低多个对象和类之间的通信复杂度。

命令模式。(保姆模式 )

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        AllCommand().build.enumerated().forEach { (command) in
            command.element.execute()
        }
        return true
}
​
// 发布对象
class AllCommand {
    var build: [Command] {
        return [WindowCommand(), ConfigCommand(), LogCommand(), RegisterCommand()]
    }
}
​
// 抽象命令
protocol Command {
    func execute()
}
​
// 接收对象
class WindowCommand: Command {
    
    var window: UIWindow?
    
    // 实现命令
    func execute() {
        let vm = RootViewModel()
        let vc = RootViewController(viewModel: vm)
        let nav = UINavigationController(rootViewController: vc)
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.makeKeyAndVisible()
        window?.backgroundColor = .white
        window?.rootViewController = nav
    }
}
​
class ConfigCommand: Command {
    func execute() {}
}
​
class LogCommand: Command {
    func execute() {}
}
​
class RegisterCommand: Command {
    func execute() {}
}
​

它拥有低耦合的设计思路,通俗点理解就是:每一个资源点都是一个命令(对象),并通过一个统一的类管理我们所需的命令,并在appdelegate中执行这个命令的集合(对象),等于我们去分别执行每一个命令所包裹的资源点。

抽象命令:声明出具备命令的接口,拥有执行命令的抽象方法
具体命令:抽象命令的具体实现,通过接收者来实现命令
接收者:执行发来的命令
发布者:发出命令
命令模式主要是为了对 【调用命令】的发布者与【实现命令】的接受着进行解耦。新的命令可以很容易的添加进去不会影响原有业务。

四、结合实际

有了上面几种种方式还需要考虑别的方式吗,答案是肯定的。其实上面的方式也有严重缺陷,面对复杂情况并不能达到想要的效果,如果只针对didFinishLaunching函数,是没有一点问题的,可我们不要忘了其他的代理方法,如:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
等等..

既然在代码间职责混乱的时候不能作为一个优质的解决方案,肯定是不尽如此人意的!

方案选型

组合设计模式,也可以完成对复杂情况的处理,将相似功能的资源放在一起,当做一个组,以组和组员的方式区分层次结构。对象组的概念,把相同特征的对象看做一个整体。如果把每个功能看做一个工具,组合功能的的却却可以将工具分门别类的规划好,像是,有些是厨卫工具,有些是家庭生活工具,我们可以把具备相同特征的工具整合在一个货架上,而货架又变成了一个组,理解起来就是subviews与view的关系,也是最容易理解的一种重构方式。

多中介者模式,我将中介者模式的一种改进,看起来和组合模式有些类似,类似于我家有多个机器人,厨房有一个,厕所有一个,客厅有一个,他们互相之间都不冲突,他们都执行我的指令并执行他们的工作,我不会对厨房机器人下达"洗厕所"指令,他们也不用分析我的错误指令。

显然随着学习的深入,设计模式仅仅提供一种思考方式,是解决问题的方案。将设计模式融入到日常开发中,定制成符合自身业务的编码方式,学习灵活运用要比生搬硬套更在长久开发中有效。

我的方式

AppDelegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
    LaunchOptionsMediator.defaultMediator.application(application, didFinishLaunchingWithOptions: launchOptions)
​
    return true
}
​
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        RemoteNotificationsDeviceTokenMediator.defaultMediator.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
    }

BasicMediator

protocol Cooperator {
    var id: String { get }
}
​
protocol LaunchOptionsColleague: Cooperator {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
}
​
protocol RemoteNotificationsDeviceTokenColleague: Cooperator {
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
}
​
​
@objc protocol Mediator {
​
    @objc optional func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
    
    @objc optional func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
​
}
​
class LaunchOptionsMediator: Mediator {
    
    static let defaultMediator = LaunchOptionsMediator([WindowObjc(), ConfigObjc()])
    
    var cooperators: [LaunchOptionsColleague] = []
    
    convenience init(_ arrs: [LaunchOptionsColleague]) {
        self.init()
        cooperators = arrs
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) {
        for cooperator in cooperators {
            print("-------------- (self)  (cooperator.id)  ---------------")
            cooperator.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
    }
    
}
​
​
class RemoteNotificationsDeviceTokenMediator: Mediator {
    
    static let defaultMediator = RemoteNotificationsDeviceTokenMediator([RemoteObjc()])
    
    var cooperators: [RemoteNotificationsDeviceTokenColleague] = []
    
    convenience init(_ arrs: [RemoteNotificationsDeviceTokenColleague]) {
        self.init()
        cooperators = arrs
    }
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        for cooperator in cooperators {
            print("-------------- (self)  (cooperator.id)  ---------------")
            cooperator.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
        }
    }
    
}
​

BasicColleague

// MARK: - LaunchOptionsColleague
class WindowObjc: LaunchOptionsColleague {
    var id: String
    var window: UIWindow?
    
    init() {
        self.id = "window窗口"
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) {
        let vm = RootViewModel()
        let vc = RootViewController(viewModel: vm)
        let nav = UINavigationController(rootViewController: vc)
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.makeKeyAndVisible()
        window?.backgroundColor = .white
        window?.rootViewController = nav
    }
    
}
​
class ConfigObjc: LaunchOptionsColleague {
    var id: String
    init() {
        self.id = "配置环境"
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) {}
    
}
​
class RemoteObjc: RemoteNotificationsDeviceTokenColleague {
    var id: String
    init() {
        self.id = "NotificationsToken"
    }
        
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {}
    
}

对项目重构的一些建议:如果你是一个旧项目,最好先把原本的代码内容,按照功能模块从一大串代码中细分成小模块,并标注清楚具体的内容,同时,根据具体情况,做出执行顺序细微调整,然后才是对appdelegate架构的整个替换。

同时我们应该注意,使用extension是swift中一个及其简单的操作,但对架构的设计会显得不痛不痒,但架构设计也不代表技术越花哨就越好,希望所学的技术都能兼容进日常最普通的业务中。

以上代码:github.com/marst123/H_… 谢谢支持!