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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 `AppDelegate` 也可以使用 `UINavigationController` 进行页面的跳转,但是这不是一个好的编程实践,因为 `AppDelegate` 主要负责应用程序的生命周期和应用级别的事件处理,不应该直接处理视图控制器的跳转逻辑。 如果需要在 `AppDelegate` 进行页面跳转,可以通过获取 `UIWindow` 的 `rootViewController` 属性来获取当前的根视图控制器,然后进行跳转。例如: ```objective-c - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 创建 UIWindow 实例 self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; // 创建根视图控制器 UIViewController *rootViewController = [[UIViewController alloc] init]; rootViewController.view.backgroundColor = [UIColor whiteColor]; // 创建 UINavigationController 实例,并设置根视图控制器 UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController]; // 将 UINavigationController 实例设置为 UIWindow 的根视图控制器 self.window.rootViewController = navigationController; // 显示窗口 [self.window makeKeyAndVisible]; // 在需要的地方进行页面跳转 UIViewController *targetViewController = [[UIViewController alloc] init]; [navigationController pushViewController:targetViewController animated:YES]; return YES; } ``` 这样做虽然可以实现页面的跳转,但是建议还是在视图控制器进行页面跳转,这样代码更加清晰,也符合 MVC 设计模式的思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值