组件 1.1.0

组件 1.1.0

Bartlomiej Nowak 维护。



组件 1.1.0

  • Bartłomiej Nowak

组件

Bitrise status Carthage compatible CocoaPods version Swift version

组织代码组件,以最小化iOS应用程序中不同功能之间的耦合。

框架由三个主要对象类型和两个支持对象类型组成

  • Router - 决定应打开哪个模块
  • Navigator - 允许无视视图层次结构地展示视图控制器
  • Module - 完全封装特定功能
  • Builder - 实例化模块并准备好它们使用
  • ModuleContainer/Container - 包含并注入依赖到其他对象中

框架的设计是为了让每个组件尽可能通用。您可以自由地用您自己的实现来替换它们,只需遵守现有的协议。

它是做什么的?

这是一个动态构建iOS应用程序的方法,提供了独特的可能

  • 灵活的视图展示,允许您在任何地方动态地推送或展示视图
  • 简化提取框架并将它们的内部结构与应用程序的其他部分完全分离。您唯一的接触点可以是具有 ContainerModule
  • 可以按任意架构模式实现功能模块,无论是 MVCMVVM+C 还是 Redux,只需使用 Module 作为顶级元素即可
  • 直接将深链接构建到应用程序结构中

并且它易于测试。

我该如何安装这个程序?

Cocoapods

将此代码添加到您的 Podfile

pod 'Components'

执行 pod install 并准备就绪。

Carthage

将此代码添加到您的 Cartfile

github "bartlomiejn\components"

执行 carthage update --platform ios 并使用您首选的方法将框架集成到项目中。

手动

将库克隆到目录中

git clone https://github.com/bartlomiejn/components

一旦它出现在那里

  1. .xcodeproj 文件拖放到您的工作区中。
  2. 编辑应用程序方案设置,在应用程序目标之前添加 Components-iOS
  3. 在项目设置中打开常规面板,将框架添加到 嵌入式二进制文件链接框架或库

如何使用它?

进入您的 AppDelegate 并输入以下代码

import Components

func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]?
) -> Bool {
    window = UIWindow(frame: UIScreen.main.bounds)
    // 1.
    let container = AppContainer()
    let navigator = Navigator(window: window!)
    let builder = Builder(navigator: navigator, container: container)
    let router = Router(navigator: navigator, builder: builder)
    builder.router = router
    // 2.
    router.register(AppModule.self)
    router.open(AppModule.self)
    return true
}
  1. 实例化所有顶级对象。最重要的是Router,它是每个应用程序模块的入口点
  2. 注册并打开模块。

现在我们已经设置了主层次结构,我们可以在AppContainer.swift中为我们的Module定义一个注入容器

import UIKit
import Components

class AppContainer: ModuleContainer {
    // 1.
    private let storyboard = UIStoryboard(name: "Main", bundle: .main)

    override init() {
        super.init()
        // 2.
        addModuleInjection(AppModule.self) { [weak storyboard] module in
            module.controller = self.storyboard.instantiateInitialViewController() as? ViewController
        }
    }
}
  1. 实例化您希望在不同模块之间共享的对象。
  2. 添加一个将执行属性注入到Module中的闭包

现在,我们需要创建一个具体的模块。让我们添加一个名为AppModule.swift的文件,并将其放入其中

// 1.
import Components

class AppModule: ModuleInterface {

    static let route = "app"
    private let router: RouterInterface
    private let navigator: NavigatorInterface
    // 2.
    var controller: ViewController!

    init(router: RouterInterface, navigator: NavigatorInterface) {
        self.router = router
        self.navigator = navigator
    }

    // 3.
    func open(_ parameters: AnyDictionary?, callback: ((AnyDictionary?) -> Void)?) {
        // 4.
        navigator.present(as: .root, controller: UINavigationController(rootViewController: controller))
    }
}
  1. class(或struct)中实现ModuleInterface
  2. 添加所需的依赖项作为属性
  3. open方法充当您模块的入口点
  4. 用我们的替换根控制器

以上发生了什么?

首先,《Router》已经实例化了《AppModule》。然后,《Builder》使用您之前在《ModuleContainer》中定义的闭包注入了依赖,在这种情况下是《AppViewController》。

一旦实例化完成,`Router` 使用 `AppModule.open` 方法让您在 `AppModule` 内部执行应用程序逻辑。

最后,《Navigator》将一个 `UIWindow` 的 `rootViewController` 替换为您刚刚给出的控制器。

请注意,《AppModule》根本不需要呈现视图。它可能只是对您的 API 进行 HTTP 请求或其他任何逻辑,通过 `callback` 返回一些结果。

默认情况下,《Router》在一个内部串行队列上同步调度的打开调用,并以异步在主线程上分发结束。

我该如何以不同的方式呈现视图?

《Navigator》中的其他呈现模式包括在顶级《UINavigationController》或顶级《presentedViewController》的堆栈中呈现,您可以从应用中的任何位置呈现视图。

我该如何传递参数或获取结果?

使用扩展的 open 方法

router.open(LoginModule.self, ["username": "username", "password": "abc123"]) { result in
    if let wasSuccessful = result["result"] as? Bool, wasSuccessful {
        happyPath()
    } else {
        errorPath()
    }
}

为什么它的类型是动态的?

您可以只使用 ModuleInterface.route 属性来识别模块,甚至不需要从打开它的文件中导入其定义

router.open("conversation")
router.open("account", ["id": "123456"])
router.open("login") { result in
    doSomethingBased(on: result)
}

这在您将框架分解并尝试打开一个模块时非常有用,因为这个模块由于紧密耦合,必须放在主束中。

我想贡献,我该如何做?

我们欢迎所有贡献。如果您有关于功能、改进、修复或希望在现有解决方案中测试一些内容的想法,请只需添加一个问题。