iOS页面架构模式:RIBS

原创:知识探索型文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 一、架构简介
  • 二、LoginBuilder
    • 1、LoginDependency
    • 2、LoginComponent
    • 3、LoginBuildable
    • 4、LoginBuilder
  • 三、LoginInteractor
    • 1、LoginRouting
    • 2、LoginPresentable
    • 3、LoginListener
    • 4、LoginInteractor
    • 5、回顾一下
  • 四、LoginRouter
    • 1、LoginInteractable
    • 2、LoginViewControllable
    • 3、LoginRouter
    • 4、routeToCreateAccount
  • 五、LoginPresenter/LoginViewController
    • 1、LoginPresentableListener
    • 2、LoginViewController
    • 3、LoginInteractor
  • 六、Root RIB
    • 1、RootBuilder
    • 2、RootRouter
    • 3、AppComponent
  • Demo
  • 参考文献

前言

一个Riblets(肋骨)被设计成这样,那和之前的VIPERMVC有什么区别呢?最大的区别在路由上面。Riblets(肋骨)内的Router不再是视图逻辑驱动的,现在变成了业务逻辑驱动。这一重大改变就导致了整个App不再是由表现形式驱动,现在变成了由数据流驱动。

每一个Riblet都是由一个路由Router,一个关联器Interactor,一个构造器Builder和它们相关的组件构成的。所以它的命名(Router - Interactor - Builder,Rib)也由此得来。当然还可以有可选的展示器Presenter和视图View。路由Router和关联器Interactor处理业务逻辑,展示器Presenter和视图View处理视图逻辑。

路由的职责

在整个App的结构树中,路由的职责是用来关联和取消关联其他子Riblet的,至于决定权则是由关联器Interactor传递过来的。在状态转换过程中,关联和取消关联子Riblet的时候,路由也会影响到关联器Interactor的生命周期。路由只包含2个业务逻辑:1.提供关联和取消关联其他路由的方法。 2.在多个孩子之间决定最终状态的状态转换逻辑。

拼装

每一个Riblets只有一对Router路由和Interactor关联器。但是它们可以有多对视图。Riblets只处理业务逻辑,不处理视图相关的部分。Riblets可以拥有单一的视图(一个Presenter展示器和一个View视图),也可以拥有多个视图(一个Presenter展示器和多个View视图,或者多个Presenter展示器和多个View视图),甚至也可以能没有视图(没有Presenter展示器也没有View视图)。这种设计可以有助于业务逻辑树的构建,也可以和视图树做到很好的分离。

举个例子,骑手的Riblet是一个没有视图的Riblet,它用来检查当前用户是否有一个激活的路线。如果骑手确定了路线,那么这个Riblet就会关联到路线的Riblet上面。路线的Riblet会在地图上显示出路线图。如果没有确定路线,骑手的Riblet就会被关联到请求的Riblet上。请求的Riblet会在屏幕上显示等待被呼叫。像骑手的Riblet这样没有任何视图逻辑的Riblet,它分开了业务逻辑,在驱动App和支撑模块化架构起了重大作用。

Riblets是如何工作的

在这个新的架构中,数据流动是单向的。Data数据流从service服务流到Model Stream生成Model流。Model流再从Model Stream流动到Interactor关联器。Interactor关联器可以向Service触发变化来引起Model Stream的改动。这个强制的要求就导致关联器只能通过Service层改变App的状态。

数据从后台到视图View上

一个状态的改变,引起服务器后台触发推送到App。数据就被Push到App,然后生成不可变的数据流。关联器收到model之后,把它传递给展示器Presenter。展示器Presentermodel转换成view model传递给视图View

数据从视图到服务器后台

当用户点击了一个按钮,比如登录按钮。视图View就会触发UI事件传递给展示器Presenter。展示器Presenter调用关联器Interactor登录方法。关联器Interactor又会调用Service call的实际登录方法。请求网络之后会把数据pull到后台服务器。

树型关系

当一个关联器Interactor在处理业务逻辑的工程中,需要调用其他Riblet的事件的时候,关联器Interactor需要和子关联器Interactor进行关联。如果调用方法是从子调用父类,父类的Interactor的接口通常被定义成监听者listener。如果调用方法是从父类调用到子类,那么子类的接口通常是一个delegate,实现父类的一些Protocol。在Riblet的方案中,路由Router仅仅只是用来维护一个树型关系,而关联器Interactor才担当的是用来决定触发组件间的逻辑跳转的角色。


一、架构简介

该架构背后的主要思想是,应用程序应由业务逻辑而不是视图驱动。展示 RIB 的最佳方法是一棵树:每个 RIB 都是一个节点,并且它可以不包含子节点,也可以包括一个或多个子节点。RIB 即 路由 + 交互器 + 构造器 (Router Interactor Builder)。

  • 路由(Router):负责相邻 RIB 之间的导航
  • 交互器(Interactor):是处理 RIB 业务逻辑的主要组件。它响应用户交互,与后端对话,并准备数据显示给用户
  • 构造器(Builder):是一个将所有 RIB 片段组合在一起的构造器

还有一个可选的视图(View)和展示器(Presenter)。View本身没有任何业务逻辑,它仅负责呈现 UI 并接受传递给 Interactor 的用户交互。Interactor拥有View,并且 View 通过委托模式与Interactor对话。Presenter基本上是View实现的协议。

例如,在View上点击登录按钮将触发Interactor中的Web任务,Interactor将告诉 Presenter 显示活动指示器。登录成功后, Interactor 将告诉 Router 导航到下一个页面。

幸运的是,现在你想要创建一个带有所有组件的 RIB 时,不需要手写所有样板代码。您可以安装和配置 Xcode自带的 Template 解决(通过Falconinstall-xcode-template.sh)。

要创建新的 RIB ,只需打开文件创建菜单,然后从列表中选择 RIB

我们将创建一个名为 LoginRIB ,并勾选 Owns corresponding view 以让 RIB 带有视图。

这个 Xcode 模板会生成以下几个文件。我们接下来会仔细研究它们中的每一个,并讨论它们的功能。


二、LoginBuilder

一个 Builder 负责创建所有 RIB 组件。

1、LoginDependency

import RIBs
 
protocol LoginDependency: Dependency 
{
    // TODO: Declare the set of dependencies required by this RIB, but cannot be
    // created by this RIB.
}

您会注意到的第一件事是,大多数组件是协议,而不是具体的类。这是 RIB 的主要特性之一,我们将在本文后面讨论。LoginDependency 用于将依赖项从其父项注入到 RIB 。例如,我们有一个 webService 用于执行登录 Web 请求。我们创建一个我们要注入的 WebServicing 协议。

protocol WebServicing: class 
{
    func login(userName: String, password: String, handler: (Result<String, Error>) -> Void)
}

现在,我们可以更新 LoginDependency 协议,为 Builder 提供对其依赖项的访问。

protocol LoginDependency: Dependency 
{
    var webService: WebServicing { get }
}

2、LoginComponent

final class LoginComponent: Component<LoginDependency> 
{ 
    // TODO: Declare 'fileprivate' dependencies that are only used by this RIB.
}

我们在这里使用的下一个组件是 LoginComponent 。我们可以声明一些仅在此 Builder 中使用的局部变量,例如设置 AdMob ID 等。在我们的示例中不需要这些局部变量,因为我们不需要任何私有依赖项。


3、LoginBuildable

protocol LoginBuildable: Buildable 
{
    func build(withListener listener: LoginListener) -> LoginRouting
}

下一个协议是 LoginBuildable ,它只有一个方法 build(withListener:) 。这里的 listener 参数是父侦听器。我们可以自由地向此构建方法添加更多参数,只需要根据需求来定制。


4、LoginBuilder

final class LoginBuilder: Builder<LoginDependency>, LoginBuildable 
{ 
    override init(dependency: LoginDependency) 
    {
        super.init(dependency: dependency)
    }
 
    func build(withListener listener: LoginListener) -> LoginRouting 
    {
        let component = LoginComponent(dependency: dependency)

        let viewController = LoginViewController()
        let interactor = LoginInteractor(presenter: viewController)

        interactor.listener = listener

        return LoginRouter(interactor: interactor, viewController: viewController)
    }
}

LoginBuilder 类实现了 LoginBuildable 协议,它是这里的主要组件。它使用 LoginDependency 创建一个 LoginComponentLoginComponent 现在封装了这个 RIB 需要的所有依赖项。

该构建器还创建一个 LoginViewControllerLoginInteractor,用于创建和返回 LoginRouter

这里是另一行重要的代码:interactor.listener = listener。这就是我们将父 Interactor 与子 Interactor 连接的方式。例如,我们有一个与 RootRIB 连接的 LoginRIB 。在这种情况下, RootInteractor 必须实现 LoginInteractor 侦听器将声明的方法。如果 LoginInteractor 有一个 dismissLogin 方法,则根 RIB 将实现此方法以分离Login流并显示主页。


三、LoginInteractor

同样, Xcode 模板会自动为您生成以下所有代码。

import RIBs
import RxSwift

1、LoginRouting

protocol LoginRouting: ViewableRouting 
{
    func routeToCreateAccount()
}

LoginRouting 是我们用来从Login RIB导航到后续RIB 的协议。假设我们希望能够导航到 CreateAccount 页面。


2、LoginPresentable

protocol LoginPresentable: Presentable 
{
    var listener: LoginPresentableListener? { get set }
    // TODO: Declare methods the interactor can invoke the presenter to present data.
}

LoginPresentable 用于响应在 Interactor 中执行的业务逻辑来更新 Login 视图。如果打开 LoginViewController ,您会注意到它实现了此协议。 LoginPresentable 还拥有一个 LoginPresentableListener 实例。这是 LoginViewControllerInteractor 进行通信并调用业务逻辑的一种方式。换句话说,这是 InteractorViewController 相互通信的方式。

如上所述,我们希望我们的视图控制器在执行 Web 任务时显示活动指示器。为了实现这一点,我们在 LoginPresentable 中添加了一个新方法 showActivityIndicator

protocol LoginPresentable: Presentable 
{
    var listener: LoginPresentableListener? { get set }
    func showActivityIndicator(_ isLoading: Bool)
}

3、LoginListener

protocol LoginListener: class 
{
    // TODO: Declare methods the interactor can invoke to communicate with other RIBs.
}

最后,我们有一个 LoginListener。还记得 LoginBuilder 中的这一行代码吗?

interactor.listener = listener

这是 Root RIB 将要实现的侦听器。这是子级 RIB 与父级进行通信的一种方式。登录完成后,我们需要通知 Root RIB,以便可以取消登录流程。

protocol LoginListener: class 
{
    func dismissLoginFlow()
}

4、LoginInteractor

final class LoginInteractor: PresentableInteractor<LoginPresentable>, LoginInteractable, LoginPresentableListener 
{
    weak var router: LoginRouting?
    weak var listener: LoginListener?
 
    // TODO: Add additional dependencies to constructor. Do not perform any logic
    // in constructor.
    override init(presenter: LoginPresentable) 
    {
        super.init(presenter: presenter)
        presenter.listener = self
    }
 
    override func didBecomeActive() 
    {
        super.didBecomeActive()
        // TODO: Implement business logic here.
    }
 
    override func willResignActive() 
    {
        super.willResignActive()
        // TODO: Pause any business logic.
    }
}

现在我们看一下 LoginInteractor 类。它有两个 weak 变量: routerlistener。这就是 Interactor 连接到其 Router 和父 Interactor 的方式。可以看到,该Interactor还拥有一个 Presenter

接下来,我们可以看到一些生命周期方法 didBecomeActivewillResignActive 。这些方法是自解释的,我们不会直接调用它们。例如,我们可以在 didBecomeActive 中执行Web任务以获取所需的数据,或者根据我们的业务逻辑进行初始视图设置。


5、回顾一下

RIB 背后的核心思想是该应用程序应由业务逻辑驱动。Interactor 就是此业务逻辑所在的地方。这里是我们使用 Interactor 控制应用程序流程的方式。

  • 我们调用 presenter 方法来更新登录 UI(我们的示例中有 showActivityIndicator
  • 我们调用 router 方法导航到子 RIB(我们的示例中有routeToCreateAccount
  • 我们调用 listener 方法与父 RIB 对话(我们的示例中有 dismissLoginFlow

四、LoginRouter

1、LoginInteractable

protocol LoginInteractable: Interactable 
{
    var router: LoginRouting? { get set }
    var listener: LoginListener? { get set }
}

LoginInteractable 是这里的主要协议,包含两个组件, LoginRoutingLoginListener。我们在 Interactor中创建它们。


2、LoginViewControllable

LoginViewControllable 用于操纵视图层次结构。因此,当 Interactor 告诉 Router 使用 LoginRouting 导航到 CreateAccount 时, Router 最终将需要显示 CreateAccount 页面。我们需要添加以下方法。

protocol LoginViewControllable: ViewControllable 
{
    // TODO: Declare methods the router invokes to manipulate the view hierarchy.
    func present(_ viewController: ViewControllable)
}

3、LoginRouter

如您所见,LoginRouter 实现了 LoginRouting 协议,因此我们需要添加必需的方法 routeToCreateAccount

final class LoginRouter: ViewableRouter<LoginInteractable, LoginViewControllable>, LoginRouting 
{
    // TODO: Constructor inject child builder protocols to allow building children.
    override init(interactor: LoginInteractable, viewController: LoginViewControllable) 
    {
        super.init(interactor: interactor, viewController: viewController)
        interactor.router = self
    }


    func routeToCreateAccount() 
    {
        ...  
    }
}

4、routeToCreateAccount

在展示其 viewController 之前,我们需要有一个CreateAccount RIB 。创建另一个 RIB 。我们不会在此 RIB 中进行任何更改,因此只需将其保留并返回 LoginRouter 即可。

要构建 CreateAccount RIBLoginRouter 需要有一个 CreateAccountBuilder 。声明一个类型为 CreateAccountBuildable 的私有变量,并更新 LoginRouter 构造器,以注入 CreateAccountBuildable 。我们没有使用具体的 CreateAccountBuilder 类型。相反,我们使用协议 CreateAccountBuildable

final class LoginRouter: ViewableRouter<LoginInteractable, LoginViewControllable>, LoginRouting 
{
    private let createAccountBuilder: CreateAccountBuildable
 
    init(
        interactor: LoginInteractable,
        viewController: LoginViewControllable,
        createAccountBuilder: CreateAccountBuildable
    ) {
        self.createAccountBuilder = createAccountBuilder
        super.init(interactor: interactor, viewController: viewController)
        interactor.router = self
    }
    
    func routeToCreateAccount() 
    {
        ...  
    }
}

现在我们可以完成 routeToCreateAccount 方法。我们使用 createAccountBuilder 构建一个 createAccountRouter。我们需要在 Build 方法中将当前的 Interactor 作为侦听器传递。我们将 createAccountRouter 作为子级附加到当前 Router。这就是我们构建 RIB 树的方式。我们调用 LoginViewControllable 方法来呈现 CreateAccount 视图控制器。

func routeToCreateAccount() 
{
    let router = createAccountBuilder.build(withListener: interactor)
    attachChild(router)
    viewController.present(router.viewControllable)
}

在这里会遇到的第一件事是以下编译器错误:

Argument type ‘LoginInteractable’ does not conform to expected type ‘CreateAccountListener’

要解决此问题,我们需要确保 LoginInteractable 实现 CreateAccountListener 协议:

protocol LoginInteractable: Interactable, CreateAccountListener 
{
    var router: LoginRouting? { get set }
    var listener: LoginListener? { get set }
}

这是另一件要记住的事情。我们使用 attachChild 方法附加 createAccountRouter 。后续需要另一种方法来关闭 CreateAccount 页面。关闭子页面后,我们必须将其 Router与当前树分离。当 viewController 不再可用时,相应的 RIB 仍在树中,我们不想看到这种状态,因为这最终可能导致内存泄漏和意外行为。为了避免这种情况,我们将在 LoginRouter 中创建一个变量保留对 CreateAccountRouter 的引用,然后将其与当前树分离。

final class LoginRouter: ViewableRouter<LoginInteractable, LoginViewControllable>, LoginRouting 
{ 
    private let createAccountBuilder: CreateAccountBuildable
    private let createAccountRouter: CreateAccountRouting?
    
    // ...
}

现在,我们更新 routeToCreateAccount 方法。我们需要将 createAccountRouter 保存到本地变量。另外,如果已经创建了子 Router ,我们需要防止再次创建Router和提供子视图控制器。

func routeToCreateAccount() 
{
    guard createAccountRouter == nil else { return }
 
    let router = createAccountBuilder.build(withListener: interactor)
    createAccountRouter = router
    attachChild(router)
    viewController.present(router.viewControllable)
}

最后,当我们要关闭 CreateAccount 页面时,在使用视图层次结构进行操作后,我们必须从当前树中分离其 Router

func detachCreateAccount() 
{
    guard let createAccountRouter = createAccountRouter else { return }
    createAccountRouter.viewControllable.uiviewController.dismiss(animated: true, completion: nil)
    detachChild(createAccountRouter)
    self.createAccountRouter = nil
}

Xcode 将显示另一个编译器错误,因此我们需要更新 LoginBuilder 并将 CreateAccountBuilder 传递给 LoginRouter 的构造器。我们使用 LoginBuilder 创建并注入一个子 Builder

final class LoginBuilder: Builder<LoginDependency>, LoginBuildable 
{
    override init(dependency: LoginDependency) 
    {
        super.init(dependency: dependency)
    }
 
    func build(withListener listener: LoginListener) -> LoginRouting 
    {
        let component = LoginComponent(dependency: dependency)
        let viewController = LoginViewController()
        let interactor = LoginInteractor(presenter: viewController)
        interactor.listener = listener
        
        let createAccountBuilder = CreateAccountBuilder(dependency: component.dependency)
        
        return LoginRouter(
            interactor: interactor,
            viewController: viewController,
            createAccountBuilder: createAccountBuilder
        )
    }
}

注意,我们将 component.dependency 用作 createAccountBuilder 依赖项。为此,我们需要 LoginDependency 来实现 CreateAccountDependency 协议。这是我们将依赖关系从父 RIB 连接到子 RIB 的方式。

protocol LoginDependency: CreateAccountDependency 
{
    var webService: WebServicing { get }
}

在我们的示例中, CreateAccountDependency 没有任何变量。如果有的话,我们需要在某些时候提供它们。在根组件中创建并保留所有依赖项,然后使用此协议继承传递它们,这很方便。我们将在本文结尾处进行此操作。到目前为止,该应用程序应该编译没有任何错误。


五、LoginPresenter/LoginViewController

import RIBs
import RxSwift
import UIKit

1、LoginPresentableListener

protocol LoginPresentableListener: class 
{
    func didTapLogin(username: String, password: String)
    func didTapCreateAccount()
}

我们只需要知道我们要在此 ViewController 上执行哪些操作即可。我们在 LoginPresentableListener 中添加两个方法。我们不会专注于 UI,但是如果您希望在实际操作中看到它,则可以继续创建一个简单的 UI。确保按钮触发正确的listener 方法。


2、LoginViewController

LoginViewController 类实现了我们之前配置的 LoginPresentable 协议(以便 Interactor 可以与 viewController 通信)。这意味着 LoginViewController 必须实现 showActivityIndicator 方法。

final class LoginViewController: UIViewController, LoginPresentable, LoginViewControllable 
{ 
    weak var listener: LoginPresentableListener?
  
    // MARK: - LoginPresentable
    func showActivityIndicator(_ isLoading: Bool) 
    {
      
    }
}

viewController 实现的下一个协议是 LoginViewControllable (以便 Router 可以修改视图层次结构)。为了符合要求, LoginViewController 必须实现当前方法。这是我们在 LoginViewController 中需要做的所有事情。同样,您可以添加缺少的 UI 按钮,文本字段和活动指示器。

final class LoginViewController: UIViewController, LoginPresentable, LoginViewControllable 
{ 
    weak var listener: LoginPresentableListener?
  
    // MARK: - LoginPresentable
    func showActivityIndicator(_ isLoading: Bool) 
    {
      
    }
    
    // MARK: - LoginViewControllable
    func present(_ viewController: ViewControllable) 
    {
        present(viewController.uiviewController, completion: nil)
    }
}

3、LoginInteractor

因为我们向 LoginPresentableListener 添加了一些方法,并且 LoginInteractor 实现了此协议,所以我们需要向 Interactor 添加缺少的方法。

final class LoginInteractor: PresentableInteractor<LoginPresentable>, LoginInteractable, LoginPresentableListener 
{ 
  // ...
  
  // MARK: - LoginPresentableListener
  
  func didTapLogin(username: String, password: String) 
  {
  
  }
  
  func didTapCreateAccount() 
  {
  
  }
}

didTapCreateAccount 必须路由到 CreateAccount RIB ,因此我们只需要调用现有的 LoginRouting 方法。

func didTapCreateAccount() 
{
     router?.routeToCreateAccount()
}

要调用登录Web任务,我们需要访问我们之前创建的 WebServicing 登录方法。我们将把 WebServicing 传递给 LoginInteractor 构造器。

final class LoginInteractor: PresentableInteractor<LoginPresentable>, LoginInteractable, LoginPresentableListener 
{ 
    // ...
    private let webService: WebServicing
 
    init(presenter: LoginPresentable, webService: WebServicing) 
    {
        self.webService = webService
        super.init(presenter: presenter)
        presenter.listener = self
    }
  
    // ...
}

Interator 中有 WebServicing ,我们可以完成登录方法。在此方法内部,我们实现了所有登录业务逻辑,显示和隐藏活动指示器,在登录成功时关闭 LoginFlow 页面,并在登录失败的情况下记录错误。

func didTapLogin(username: String, password: String)
{
    presenter.showActivityIndicator(true)
    webService.login(userName: username, password: password)
    { [weak self] result in
       self?.presenter.showActivityIndicator(false)
       switch result
       {
       case let .success(userID):
           // do something with userID if needed
           self?.listener?.dismissLoginFlow()
       case let .failure(error):
           // log error
       }
    }
}

我们还添加另一个 LoginPresentable 方法 showErrorAlert,如果登录失败,该方法将通知用户。

protocol LoginPresentable: Presentable 
{
    var listener: LoginPresentableListener? { get set }
    func showActivityIndicator(_ isLoading: Bool)
    func showErrorAlert()
}

编译器将确保您已在 LoginViewController 中实现此方法。在 login 失败的情况下调用此方法。

webService.login(userName: username, password: password)
{ [weak self] result in
   self?.presenter.showActivityIndicator(false)
   switch result
   {
   case let .success(userID):
       // do something with userID if needed
       self?.listener?.dismissLoginFlow()
   case let .failure(error):
       // log error
       self?.presenter.showErrorAlert()
   }
}

最后,我们必须更新 LoginBuilder 并将 WebServicing 依赖项传递到 LoginInteractor 中。

final class LoginBuilder: Builder<LoginDependency>, LoginBuildable
{
    override init(dependency: LoginDependency)
    {
        super.init(dependency: dependency)
    }
 
    func build(withListener listener: LoginListener) -> LoginRouting
    {
        let component = LoginComponent(dependency: dependency)
        let viewController = LoginViewController()
        let interactor = LoginInteractor(presenter: viewController, webService: component.dependency.webService)
        interactor.listener = listener
        
        let createAccountBuilder = CreateAccountBuilder(dependency: component.dependency)
        
        return LoginRouter(
            interactor: interactor,
            viewController: viewController,
            createAccountBuilder: createAccountBuilder
        )
    }
}

六、Root RIB

现在,我们为应用程序提供了完整的登录模块。如果您想查看全部内容,则必须添加一些缺失的部分。创建一个 Root RIB ,它将成为 Login RIB 的父级(您应该能够使用上面提供的相同步骤将登录连接到 root。主要的区别是在RootRouterRootBuilder 中,因为它是一个顶级 RIB,所以没有父 RIB)。

1、RootBuilder

protocol RootDependency: Dependency
{
    
}
final class RootComponent: Component<RootDependency>
{
    private let rootViewController: RootViewController
 
    init(dependency: RootDependency,
         rootViewController: RootViewController)
    {
        self.rootViewController = rootViewController
        super.init(dependency: dependency)
    }
}

除了创建 RootRouting ,我们还需要创建 LaunchRouting(为顶级 RIB 设计的特定 RIB 组件)。

protocol RootBuildable: Buildable
{
    func build() -> LaunchRouting
}
final class RootBuilder: Builder<RootDependency>, RootBuildable
{
    override init(dependency: RootDependency)
    {
        super.init(dependency: dependency)
    }
 
    func build() -> LaunchRouting
    {
        let viewController = RootViewController()
        let component = RootComponent(
            dependency: dependency,
            rootViewController: viewController
        )
        
        let interactor = RootInteractor(presenter: viewController)
 
        return RootRouter(
            interactor: interactor,
            viewController: viewController
        )
    }
}

2、RootRouter

RootRouter 还将继承自 LaunchRouter 而不是 ViewableRouter , 前者是特定于启动的路由协议。

final class RootRouter: LaunchRouter<RootInteractable, RootViewControllable>, RootRouting 
{
    override init(interactor: RootInteractable, viewController: RootViewControllable) 
    {
        super.init(interactor: interactor, viewController: viewController)
        interactor.router = self
    }
}

3、AppComponent

我们还需要创建一个 AppComponent,它继承自具有 EmptyDependency 泛型类型的 Component 。该组件有我们希望使用依赖协议传递的大多数依赖。您可以创建一个继承自 WebServicing 协议的WebService类,并将其保留为 AppComponent 中的变量。

final class AppComponent: Component<EmptyDependency>, RootDependency 
{
    ...
}

AppDelegate 中,我们需要使用此 AppComponent 创建一个 RootRouter ,并在当前窗口中启动它。

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{
    var window: UIWindow?
    private var launchRouter: LaunchRouting?
 
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
    {
        
        let window = UIWindow(frame: UIScreen.main.bounds)
        self.window = window
        
        let launchRouter = RootBuilder(dependency: AppComponent()).build()
        self.launchRouter = launchRouter
        launchRouter.launch(from: window)
        
        return true
    }
}

Demo

Demo在我的Github上,欢迎下载。
IOSAdvancedDemo

推荐Demo
RIBsTutorialExample

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容