[译]如何将初始化代码从 AppDelegate 中移除

翻译自:https://christiantietze.de/posts/2015/10/bootstrapping-appdelegate/

我为Word Counter开发了一个简单的框架,用来在启动时管理和初始组件的引导代码。通过使用这种方法,优化掉了 AppDelegate 中 60 行初始化代码。

  • 组件对它自己进行初始化。
  • 已经初始化过的组件为一个队列,当有新的组件初始化成功,它会被放进这个队列中。

通过这种方式,你可以构建一系列需要初始化的组件的列表,然后依次初始化他们。

这个框架非常简单:

enum BootstrappingError: ErrorType {
    case ExpectedComponentNotFound(String)
}

protocol Bootstrapping {

    func bootstrap(bootstrapped: Bootstrapped) throws
}

struct Bootstrapped {

    private let bootstrappedComponents: [Bootstrapping]

    init() {
    
        self.bootstrappedComponents = []
    }

    init(components: [Bootstrapping]) {
    
        self.bootstrappedComponents = components
    }

    func bootstrap(component: Bootstrapping) throws -> Bootstrapped{

        try component.bootstrap(self)
    
        return Bootstrapped(components: bootstrappedComponents.add(component))
    }

    func component<T: Bootstrapping>(componentType: T.Type) throws -> T {
    
        guard let found = bootstrappedComponents.findFirst({ $0 is T }) as? T else {
            throw BootstrappingError.ExpectedComponentNotFound("\(T.self)")
        }
    
        return found
    }
}

bootstrap(_:) 控制组件的初始化,如果成功,初始化队列增长。

component(_:) 查找已经被初始化的组件。如果找到,返回找到的组件。如果这个组件未被成功初始化或者没有找到,抛出.ExpectedComponentNotFound

注意,我没有将 bootstrappedComponents 定义为一个变量。也没有将bootstrap(:_)方法设计为mutate。通过reduce进行管道式调用:

// Bootstrap the sequence of components
func bootstrapped(components: [Bootstrapping]) throws -> Bootstrapped {

    return try components.reduce(Bootstrapped()) { bootstrapped, next in

        return try bootstrapped.bootstrap(next)
    }
}

bootstrapped(_:) 最初执行的时候,持有一个空的组件序列,尝试初始化所有的组件并最终返回初始化后的组件序列。这很像使用for-each来进行迭代,只是更加简洁,代码:

var result = Bootstrapped()
for nextComponent in components {
    result = result.bootstrap(nextComponent)
}
return result

为了保持示例足够简单,组件在初始化时并没有接受参数,在正式的代码中,这块儿可能有点不一样。

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    // ...
    
    let components: [Bootstrapping] = [
        ErrorReportBootstrapping(),
        PersistenceBootstrapping(),
        DomainBootstrapping(),
        ApplicationBootstrapping()
    ]

    // Will be set during bootstrapping
    var showWindowService: ShowMainWindow!

    func applicationDidFinishLaunching(aNotification: NSNotification) {

        // Prepare everything
        bootstrap()

        // Then do something, like displaying the main window
        showWindow()
    }
    
    func bootstrap() {
        
        do {

            let bootstrapped = try bootstrapped(components)
            let application = try bootstrapped.component(ApplicationBootstrapping.self)
    
            showWindowService = application.showWindowService
        } catch let error as NSError {
            showLaunchError(error)
            fatalError("Application launch failed: \(error)")
        }
    }

    func bootstrapped(components: [Bootstrapping]) throws -> Bootstrapped {

        return try components.reduce(Bootstrapped()) { bootstrapped, next in
    
            return try bootstrapped.bootstrap(next)
        }
    }
    
    func showLaunchError(error: NSError) {

        let alert = NSAlert()
        alert.messageText = "Application launch failed. Please report to [email protected]"
        alert.informativeText = "\(error)"

        alert.runModal()
    }
    
    func showWindow() {

        showWindowService.showWindow()
    }
}

错误报告组件,如例所示。非常简单,也很基础。

class ErrorReportBootstrapping: Bootstrapping {

    let errorHandler = ErrorHandler()
    let invalidDataHandler = InvalidDataHandler()

    func bootstrap(bootstrapped: Bootstrapped) throws {
    
        ErrorAlert.emailer = TextEmailer()
    
        // Add objects to a global registry of services used application-wide.
        ServiceLocator.sharedInstance.errorHandler = errorHandler
        ServiceLocator.sharedInstance.missingURLHandler = invalidDataHandler
        ServiceLocator.sharedInstance.fileNotFoundHandler = invalidDataHandler
    
    }
}

组件初始化的时候可以调用其他已经初始化的组件:

import Foundation

class DomainBootstrapping: Bootstrapping {

    var bananaPeeler: BananaPeeler!

    func bootstrap(bootstrapped: Bootstrapped) throws {
      
        // may throw .ExpectedComponentNotFound("PersistenceBootstrapping")
        let persistence = try bootstrapped.component(PersistenceBootstrapping.self)
    
        bananaPeeler = BananaPeeler(bananaStore: persistence.bananaStore)
    }
}

最后初始化的 ApplicationBootstrapping 可以使用已经初始化的DomainBootstrapping 组件,并通过 bananaPeeler,并把它用于窗口控制器的事件处理。

一些初始化组件为 VIPER 架构的 wireframes 做类似的工作,他们准备 interactor,presenter和视图。全部的初始化工作包括设置 wireframes,然后构建 AppDenpendencies,接着在其中初始化 Core Data 还有一些其他的工作。

将初始化代码拆分成独立部分,并有序的组织在一起,这就是这个框架所做的事。我认为它比把一切都堆在 AppDependencies 对象中要好。

相关推荐