翻译自: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 support@christiantietze.de"
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 对象中要好。