原因-逻辑-效果 7.2.1

原因-逻辑-效果 7.2.1

Daniel Tartaglia 维护。



 
依赖
RxSwift~> 6.0
RxCocoa~> 6.0
 

  • 作者
  • Daniel Tartaglia

CLE-Architecture-Tools

文件模板可用于创建新的 Rx 场景(使用这些中的一个代替直接创建视图控制器。)将文件夹放置在 Library/Developer/Xcode/Templates/

实用文件夹包含应包含在项目中的支持代码(如果您使用 SPM 或 Cocoapods,它们将自动包含。)从架构角度来看,最重要的文件是 "Stage.swift" 和 "Scene.swift"。其他文件包含我在 80% 或更多项目中使用的代码。

工具文件夹包含我开发但大多数项目中不需要的辅助工具。如果您想使用其中任何一项,您必须手动将其拖入项目。

需求

  • RxSwift
  • RxCocoa

CLE 的一个基本哲学是将 imperative shell 与 functional core 结合(首先由 Gary Bernhardt 解释,并由 Matt Diephouse 在 Swift 中扩展)。这意味着,在许多其他方面,副作用(imperative 部分)位于系统的外部边缘,并且不会 注入 到逻辑中。这与我所知的所有其他架构风格都有很大不同。这种架构风格的全局点是你永远不需要在测试中 Mock 或 Stub 任何代码。

注意,上面我没有提到Fakes,只有Mocks和Stubs。这是因为即使是CLE也要求您使用TestObservable,它是一个Fake。如果您在使用Store类的环境类型时小心谨慎,并确保您只在测试时通过它传递Fakes,您可以在使用时遵循这一理念,但它并不会像我的其他架构那样严格要求。

我对那些感兴趣的人愿意深入探讨。如果您有任何问题或想讨论,欢迎在社交媒体上联系我,或在github上提出问题。


我最近帮助了某人,并这样描述了CLE架构。或许这能帮到其他人...

每当您需要执行副作用并需要结果时,模板类似于

let resultOfEffect = trigger
    .withLatestFrom(additionalDataNeeded) // if any
    .flatMap { performSideEffect($0) }
    .share(replay: 1) // if you are using the result in multiple places.

如果您不需要结果,那么它就是

trigger
    .withLatestFrom(additionalDataNeeded) // if any
    .subscribe(onNext: { performEffect($0) })
    .disposed(by: disposeBag)

或者

_ = trigger
    .withLatestFrom(additionalDataNeeded) // if any
    .take(until: event) // often `rx.deallocating`
    .subscribe(onNext: { performEffect($0) })

有时触发器很复杂。有时additionalDataNeeded很大。无论如何,上面的两个模板将满足大多数需求。


已发布2.0版本。我必须提高版本号,因为根据构建场景的确切方式,它可能是一个破坏性的更改。对于库的大多数应用,您根本不需要更新代码。

那么,有什么大变化,但变化不大?

在库的1.x版本中,场景创建方法创建了视图控制器,然后加载数据控制器视图并在它们关联闭包中调用。必须加载视图以确保所有子视图都已经构建,因此您可以通过按钮、文本框等进行连接。然而,这意味着在将这些视图添加到导航或展示层之前,视图就已经完全加载。对于2.x版本,我使连接受托功能泄漏,现在库在UIKit加载视图后会调用它。这发生在控制器已经附加到导航或展示层之后。这意味着对于2.x版本,您将有机会访问所有通常可以访问的UIViewController属性,即使它们是在与视图控制器层次结构关联之后分配的。


安装

三种安装方式

  1. 将Utilities文件夹拖到您的xcode工作空间下的项目名称文件夹中。

  2. 使用Swift包管理器。

  3. 使用CocoaPods

use_frameworks!

target 'YOUR_TARGET_NAME' do
   pod 'Cause-Logic-Effect'
end

替换YOUR_TARGET_NAME,然后在Podfile目录中输入

$ pod install

使用

这个库背后的理念是提供一个简单的方法来将视图控制器作为可观察的资源包装,以便将其作为一个简单的异步事件。本质上,您的代码将能够以与它与服务器或数据库相同的透平映射和平绑定方式与其视图控制器一起工作。

场景

核心类型是Scene,它由一个视图控制器和发出所需用户提供的值以及完成时的停止事件的可观察者组成。或者,调用代码可以调用Scene的可观察者的dispose()方法,如果它想在完成前关闭视图控制器。

可以通过调用scene方法从视图控制器创建一个Scene。存在一个实例方法用于从已经存在的视图控制器构建一个Scene,或者可以使用静态方法scene,这将从一个Storyboard加载一个视图控制器并使用它创建一个Scene。

显示场景

一旦创建了一个Scene,就可以通过许多方式显示它。您可以调用它的present方法,将其push到导航控制器,或简单地调用它的show方法。所有与正常视图控制器一起工作的相同方式。无论您选择何种方式来显示您的Scene,都可以保证当它被丢弃时,它会使用正确的显示方法的反方法隐藏自己。(因此,如果呈现,它将关闭;如果推送,它将弹出等。)显示Scene的函数在"Stage.swift"文件中。

一个示例场景

几乎每个程序都在某个时刻使用Alert控制器。该库为UIAlertController提供了一些方法,用于实现视图控制器广泛使用的常见操作。其中包括connectOK(),当用户点击“确定”按钮时返回一个发出Void的Observable,以及一个connectChoice,它将为每个提供的选项添加一个按钮,包括一个取消按钮,并返回一个发出用户选择的Observable(如果用户取消则返回nil)。

与流程一起使用

场景也可以表示多个子场景,这些场景一起作为一个“流程”或“过程”工作。下面是一个这样的结构示例。流程是一个返回场景的函数,并且该场景实际上封装了多个子场景,这些子场景协同工作以完成一项工作。如果任何一个子场景完成了,整个流程就会完成。请注意,与典型的Coordinator类型不同,您不需要管理任何资源。在任何时候,都不需要手动跟踪当前显示哪些视图控制器。

使用示例

当用户在我的一个应用程序中点击“忘记密码”链接时

forgotPasswordButton.rx.tap
    .bind(onNext: presentScene(animated: true, scene: forgotPasswordFlow))
    .disposed(by: disposeBag)

忘记密码流程将按顺序显示三个屏幕

func forgotPasswordFlow() -> Scene<Void> {
    // This scene asks the user to enter their phone number.
    let forgotPassword = ForgotPasswordViewController.scene { $0.phoneNumber() }

    // Once we get the phone number, send a one-time password to the user and ask them to enter it.
    let otpResult = forgotPassword.action
        .observe(on: MainScheduler.instance)
        .flatMapFirst(presentScene(animated: true) { phoneNumber in
            OTPViewController.scene { $0.passwordResetToken(forPhoneNumber: phoneNumber) }
        })

    // After they enter the OTP, allow them to reset their password.
    let resetPasswordResult = otpResult
        .observe(on: MainScheduler.instance)
        .flatMapFirst(presentScene(animated: true) { token in
            ResetPasswordViewController.scene { $0.resetPassword(withToken: token) }
        })

    // When `resetPasswordResult` completes, the entire flow will automatically unwind.
    return Scene(controller: forgotPassword.controller, action: resetPasswordResult)
}

强制使用

如果您正在从强制代码转换为使用此库,以下函数将非常有用

func call<T>(_ fn: (()) -> Observable<T>) -> Observable<T> {
	fn(())
}

func final(_ fn: (()) -> Void) {
	fn(())
}

call函数用于视图控制器向调用者返回信息时,否则使用final函数。您可以将这些函数包装在Stage.swift文件中的函数周围。例如

final(presentScene(animated: true) {
    UIAlertController(title: "Greeting", message: "Hello World", preferredStyle: .alert)
        .scene { $0.connectOK() }
})

_ = call(presentScene(animated: true, over: button) {
    UIAlertController(title: nil, message: "Which One?", preferredStyle: .actionSheet)
        .scene { $0.connectChoice(choices: ["This One", "That One"]) }
})
.subscribe(onNext: {
    print("A choice was made:", $0 as Any)
})

其他类型

库中的其他类型/方法/函数是我用在至少80%的应用程序中的辅助功能。

ActivityTrackerErrorRouter 类型用于跟踪网络请求。当您有一个自我反馈的 Observable 时,可以使用 cycle 函数。使用 Identifier 类型为 Identifiable 类型创建 id。RxHelpers 包含一些杂项方法,以使映射和观察者成功更容易处理。"UIColor+Extensions" 文件包含一个方便的初始化函数,可从一个十六进制值创建颜色。"UIViewController+Rx.swift" 文件包装一些用于内部处理视图控制器的基本函数。它包括在那些罕见的情况下,您想在天窗控制器完成之前将其从天窗控制器中移除的 dismissSelfpopSelf