[译]如何将初始化代码从 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 对象中要好。