MOReactor 2.0

MOReactor 2.0

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2016年5月
SPM支持 SPM

Rui Peres维护。



简介

Reactor 提供了一个具有最小配置的 数据模型层。它利用以下元素来实现这一点

Reactor 然后使用流(由 ReactorFlow<T> 表示),通常在应用程序中可以看到。例如

  1. 持久化的数据是否存在且有效?
    1. 是的:使用它
    2. 不是:从网络获取数据
      1. 我们有互联网连接吗?
        1. :发起请求。
          1. 解析数据并将其持久化(发送可能发生的任何错误)
          2. 请求失败:发送错误

        2. 不是:发送错误

该特定流程由 Reactor 内置提供。未来我们将提供更多。

什么是流程? 🙄

流程不过是事件的流,在我们的情况下,它由不同的部分组成(网络、解析和持久化)。

如果...使用 Reactor

  • 您正在启动一个新项目。🌳
  • 您正在定义您的模型层。 🛠
  • 您正在创建原型或演示,并且需要快速完成的工作。🚀
  • 您对 ReactiveCocoa 感到不够自在,需要一些设置上的帮助。🆒
  • 您已经有了模型层,但认为 Reactor 可以使您的流程更加规范。

如果...不适用

  • 您有一个不适用于 ReactorFlow<T> 的不寻常流程。⛔️
  • 您已经有模型层,并且认为它不会以任何方式对您有益。😞
  • 在查看 高级使用 后,Reactor 无法提供您需要的功能。😭😭

如何使用

基本配置

为了使 Reactor 工作,您需要确保您的模型对象符合 Mappable 协议。此协议允许您编码和解码对象。这是解析从网络获取的对象并将其存储在磁盘上所必需的。

Author结构体为例(可以在单元测试中找到),第一步是使Author结构体符合Mappable协议。

struct Author {
  let name: String
}

extension Author: Mappable { 

  static func mapToModel(object: AnyObject) -> Result<Author, MappedError> {

  guard
    let dictionary = object as? [String: AnyObject],
    let name = dictionary["name"] as? String
    else { return Result(error: MappedError.Custom("Invalid dictionary @ \(Author.self)\n \(object)"))}

    let author = Author(name: name)

    return Result(value: author)
  }

  func mapToJSON() -> AnyObject {
    return ["name": self.name]
  }
}

注意:上面的实现只是一个例子,您可以使用您喜欢的任何方式。

第一个函数mapToModel允许创建模型对象(JSON➡️模型)。第二个函数mapToJSON是它的逆过程(模型➡️JSON)。

第二步是

let baseURL = NSURL(string: "https://myApi.com")!
let configuration = FlowConfiguration(persistenceConfiguration: .Enabled(withPath: "path_to_persistence"))

let flow: ReactorFlow<Author> = createFlow(baseURL, configuration: configuration)
let reactor: Reactor<Author> = Reactor(flow: flow)

现在,你已经准备好了reactor,它公开了两个函数

func fetch(resource: Resource) -> SignalProducer<T, Error>
func fetchFromNetwork(resource: Resource) -> SignalProducer<T, Error>

我们发现这是最常见的两种场景

  1. 当你到达新屏幕时,试图获取一些数据。在这种情况下,Reactor首先检查持久性,如果失败,将回退到网络。
  2. 你需要新鲜的数据,所以Reactor将使用网络。

最后一部分是Resource,它只是一个封装请求如何执行的结构体

  • 路径
  • 查询
  • 主体
  • HTTP头
  • HTTP方法

配置

为了额外灵活,您可以使用CoreConfigurationFlowConfiguration协议。

CoreConfiguration协议定义了Reactor的行为

public protocol CoreConfiguration {
/// When enabled, you should pass the path where it will be stored
/// Otherwise it's disabled
var persistenceConfiguration: PersistenceConfiguration { get }
/// If the `saveToPersistenceFlow`, should be part of the flow.
/// Should be `false` when the flow shouldn't
/// wait for `saveToPersistenceFlow` to finish (for example it takes
/// a long time).
/// Note: if you set it as `false` and it fails, the failure will be
/// lost, because it's not part of the flow, but injected instead .
/// `true` by default.
var shouldWaitForSaveToPersistence: Bool { get }
}

FlowConfiguration协议定义了Reactor Flow的创建方式

public protocol FlowConfiguration {
/// If persistence should be used.
/// `true` by default.
var usingPersistence: Bool { get }
/// If reachability should be used.
/// `true` by default.
var shouldCheckReachability: Bool { get }
/// If the parser should be strict or prune the bad objects.
/// Pruning will simply remove objects that are not parseable, instead
/// of erroring the flow. Strict on the other hand as soon as it finds
/// a bad object will error the entire flow.
/// Note: if you receive an entire batch of bad objects, it will default to
/// an empty array. Witch leads to not knowing if the server has no results or
/// all objects are badly formed.
/// `true` by default.
var shouldPrune: Bool { get }
}

FlowConfiguration协议在以下方法中使用

public func createFlow<T where T: Mappable>(baseURL: NSURL, configuration: FlowConfigurable) -> ReactorFlow<T>
public func createFlow<T where T: Mappable>(connection: Connection, configuration: FlowConfigurable) -> ReactorFlow<T>
public func createFlow<T where T: SequenceType, T.Generator.Element: Mappable>(baseURL: NSURL, configuration: FlowConfigurable) -> ReactorFlow<T>
public func createFlow<T where T: SequenceType, T.Generator.Element: Mappable>(baseURL: NSURL, configuration: FlowConfigurable) -> ReactorFlow<T>

这些是方便的提供即用ReactorFlow的方法。请注意,如果您想使用自定义持久性(CoreData,Realm,SQLite等),您应自行创建ReactorFlow。原因是因为默认的持久性类(InDiskPersistence.swift)需要一个路径来保存数据。这可能与其他方法不合适(请查阅使用第三方依赖关系部分)。

无持久性

如果持久数据不合适,您可以

let baseURL = NSURL(string: "https://myApi.com")!
let configuration = FlowConfiguration(persistenceConfiguration: .Disabled)
let flow: ReactorFlow<Foo> = createFlow(baseURL, configuration: configuration)
let reactor: Reactor<Foo> = Reactor(flow: flow)

至于mapToJSON函数,您可以简单地返回一个NSNull

func mapToJSON() -> AnyObject {
  return NSNull()
}

高级用法

介绍

为了充分利用Reactor,请记住以下几点(这些都是ReactorFlow<T>的属性)

var networkFlow: Resource -> SignalProducer<T, Error>
var loadFromPersistenceFlow: Void -> SignalProducer<T, Error>
var saveToPersistenceFlow: T -> SignalProducer<T, Error>

所有三个属性都故意设置为可变(var),这样您就可以扩展特定的行为。例如,您可能对loadFromPersistenceFlow失败的原因感兴趣并记录下来。在默认流中,这是不可能的,因为如果loadFromPersistenceFlow失败,网络流将启动,错误将丢失。

完成这种方法的一种方式是创建默认流,然后扩展它

let reactorFlow: ReactorFlow<Author> = ...

let extendedPersistence = reactorFlow.loadFromPersistenceFlow().on(failure: { error in print(error) })
reactorFlow.loadFromPersistenceFlow =  { extendedPersistence }

您可以进一步分解流,因为所有核心组件都在公共API中公开。具体来说

Reactor提供的默认流(介绍)您可以自由使用,但并非绑定。在创建自己的流时,请牢记以下事项

Reactor<T>fetch函数不变

  • loadFromPersistenceFlow始终先调用。如果它失败,将调用fetchFromNetwork

Reactor<T>fetchFromNetwork函数不变

  • 网络流(networkFlow)将始终首先被调用,如果调用成功,则会被saveToPersistenceFlow跟随。

使用第三方依赖项

Reactor与其他依赖项配合得相当好,并且从您的角度来说,需要做的努力最小。在前一节中,我们看到了一个ReactorFlow的三个基本部分。

var networkFlow: Resource -> SignalProducer<T, Error>
var loadFromPersistenceFlow: Void -> SignalProducer<T, Error>
var saveToPersistenceFlow: T -> SignalProducer<T, Error>

正如所提到的,我们鼓励您根据需求对它们进行修改。对于第三方依赖项,您必须做到这一点。以下是一个如何使Alamofire兼容性的示例步骤。

  1. 使用ReactiveCocoa包装Alamofire。您可以在这里这里这里看到一个示例。这是一项相对简单的工作,并且有大量示例。
  2. 制作之前提及的方法使用的NSError成为一个Error。您可以使用mapError操作符。然后将其转换为Error.Network
  3. 这现在将取决于您是否有([]);
    1. 如果有的话,则只需要将您之前包装的Alamofire请求与其连接即可。理想情况下,您将有一个具有以下签名的函数:用于解析器的NSData -> SignalProducer<T, Error>。组合因此变得简单:alamofireCall().flatMap(.Latest, transformation: parse)(具体示例这里)。
    2. 如果没有,则可以利用Mappable协议和Reactor提供的parse函数。有了这些,您可以遵循这一

有了所有这些,最后一部分是

let persistenceHandler = InDiskPersistenceHandler<MyModel>(persistenceFilePath: persistencePath)
let loadFromPersistence = persistenceHandler.load
let saveToPersistence =  persistenceHandler.save

let reactorFlow: ReactorFlow<MyModel> = ReactorFlow(network: myNetworkFlow, loadFromPersistenceFlow: loadFromPersistence, saveToPersistence: saveToPersistence)

createFlow家族方法在内部遵循这种方法,因此你应该查看它们

其他第三方依赖项将遵循相同的方法

  1. 包装依赖与ReactiveCocoa
  2. 使其与流程签名兼容
  3. 创建适合您的ReactorFlow

此方法的另一个好处是,它强加了代码的清晰分离和解耦。如果您将持久性与网络耦合,那么使用Reactor将相当困难。

许可

Reactor根据MIT许可版2.0授权。您可以在此处查看许可文件

版权所有 © 2015 MailOnline

抬头图片来自Henrique Macedo