天天看点

[译] iOS 响应式编程:Swift 中的轻量级状态容器iOS 响应式编程:Swift 中的轻量级状态容器

<b>本文讲的是[译] iOS 响应式编程:Swift 中的轻量级状态容器,</b>

<b></b>

在客户端架构如何工作上,每一个 iOS 和 MacOS 开发者都有不同的细微见解。从最经典的苹果框架所内嵌的

这篇文章并不会回答你的问题,因为正确的答案是依据环境而定的。我想要强调的是一个我很喜欢并且经常看到的基本方法,名为状态容器。

实质上,状态容器只是一个围绕信息的封装,是数据安全输入输出的守护者。他们不是特别在意数据的类型和来源。但是他们非常在意的是当数据改变的时候。状态容器的中心思想就是,任何由于状态改变产生的影响都应该以有组织并且可预测这种方式在应用里传递。

状态容器以与线程锁相同的方式提供安全的状态。

就像 <code>ReSwift</code> 这样的响应式库,状态容器将 <code>Action</code> 和 <code>View</code> 之间的缺口桥联为单向数据流的一部分。然而即使没有其他两个组件,状态容器也很强力。实际上,他们可以做的比这些库使用的更多。

在这篇文章中,我会演示一个基本的状态容器实现,我已经把它用于各种没有引入大型架构库的项目中。

让我们从构建一个基本的 <code>State</code> 类开始。

这个基类封装了一个任何 <code>Type</code> 的 <code>_value</code>,通过一个 <code>key</code> 关联,并声明了一个提供<code>defaultValue</code> 的初始化器。

为了读取我们状态容器的当前值,我们要创建一个计算属性 <code>value</code>。

为了改变状态,我们还要创建一个 <code>modify(_newValue:)</code> 函数。虽然我们可以允许直接访问设置器,但在这里的目的是围绕状态改变来定义结构。在使用简单属性设置器的方法中,通过与我们 API 通信修改状态产生的影响。因此,所有的状态改变都必须通过这个方法来达成。

为了有趣一些,我们自定义一个运算符!

<code>didModify()</code> 是我们状态容器中最重要的一部分,因为它允许我们定义在状态改变后所触发的行为。为了能够在任何时候这种情况发生时能够执行自定义的逻辑,<code>State</code> 的子类可以覆盖这个方法。

<code>didModify()</code> 也扮演着另一个角色。如果我们通用的 <code>Type</code> 是一个 <code>class</code>,状态器就可以无需知道它就可以更改它的属性。因此,我们暴露出 <code>didModify()</code> 方法,以便这些类型的更改可以手动传播(见下文)。

这是在处理状态时使用引用类型的固有危险,所以我建议尽可能使用值类型。

下面是如何使用我们 <code>State</code> 类的最基本的例子:

我们也可以使用<code>可选</code>类型:

改变状态很容易:

如果我们有无价值的类型(比如在状态改变时,不触发 <code>didSet</code> 的类型),我们调用<code>didModify()</code> 方法,让 <code>State</code> 知道这个改变:

现在我们已经建立了一个基本的状态容器,让我们来扩展一下,让它更强大。通过我们的<code>didModify()</code> 方法,我们可以用特定子类的形式添加功能。让我们添加一种方式,来“监听”状态的改变,这样我们的 UI 组件可以在发生更改时自动更新。

第一步,让我们定义一个这样的状态监听器:

在状态改变时,监听器会在它选择的 <code>stateListenerQueue</code> 上收到 <code>stateModified(_state:)</code> 调用,默认是 <code>DispatchQueue.main</code>。

下一步,我们定义一个专门的子类,叫做 <code>MonitoredState</code>,它会对监听器保持弱引用,并通知他们状态的改变。一个简单的实现方式是使用 <code>NSHashTable.weakObjects()</code>。

无论何时 <code>didModify</code> 被调用,我们的 <code>MonitoredState</code> 类调用 <code>stateModified(_state:)</code> 上的监听者,简单!

为了添加监听器,我们要定义一个 <code>attach(listener:)</code> 方法。和上面的内容很像,在我们的<code>listeners</code> 属性上,使用 <code>listenerLockQueue</code> 来设置一个读写锁。

现在可以监听任何封装在 <code>MonitoredState</code> 里任何值的改变了!

下面是一个如何使用我们新的 <code>MonitoredState</code> 类的例子。假设我们在 <code>MonitoredState</code> 容器中追踪设备的位置:

我们还需要一个视图控制器来展示当前设备在地图上的位置:

由于我们需要在 <code>deviceLocation</code> 改变的时候更新地图,所以要把 <code>LocationViewController</code> 扩展为一个 <code>StateListener</code>:

然后记住使用 <code>attach(listener:)</code> 把视图控制器附加到状态。实际上,这个操作可以在<code>viewDidLoad</code>,<code>init</code> 或者任何你想要开始监听的时候来做。

现在我们正监听 <code>deviceLocation</code>,一旦我们从 <code>CoreLocation</code> 得到一个新的定位,我们所要做的只是改变我们的状态容器,我们的视图控制器会自动的更新位置!

值得注意的是,由于我们使用了一个弱引用 <code>NSHashTable</code>,在视图控制器被销毁时,<code>allListeners</code> 属性永远也不会有 <code>deviceLocation</code>。没有必要“移除”监听器。

记住,在真实的使用场景里,要确保视图控制器的 <code>view</code> 在执行更新 UI 之前是可见的。

OK,现在我们正在获得好的东东。我们可以把现在所需要的一切装在状态容器里,并且保持可以随时随地使用。

我们现在有一个唯一的 <code>key</code> 用于与后备存储关联。

我们知道值的 <code>Type</code>,通知它应该如何保持。

我们知道什么时候值需要从存储器中加载,使用 <code>init(_defaultValue:key:)</code> 方法。

我们知道什么时候值需要被保存在存储器中,使用 <code>didModify()</code> 方法。

让我们创建一个状态容器,它可以自动地保存任何改变到 <code>UserDefaults.standard</code> 中,并且在初始化的时候重新加载之前的这些值。它同时支持可选类型和非可选类型。他也会自动序列化和反序列化符合 <code>NSCoding</code> 的类型,即使 <code>UserDefaults</code> 并没有直接支持 <code>NSCoding</code> 的使用。

这里是代码,我会在下面讲解。

我们的初始化方法检查 <code>UserDefaults.standard</code> 是否已经包含一个由 <code>key</code> 对应的值。

如果我们能加载一个对象,并且它刚好是基本类型,我们可以立即使用它。

如果我们加载的是 <code>Data</code>,那么使用 <code>NSKeyedUnarchiver</code> 解压,它会被 <code>NSCoding</code> 存储,然后我们立即使用它。

如果 <code>UserDefaults.standard</code> 里没有和 <code>key</code> 匹配的值,我们就使用已提供的<code>defaultValue</code>。

在状态改变的时候,我们想要自动保存我们的状态,这样做的方法依赖于 <code>Type</code>

如果基本类型是 <code>Optional</code> 的,并且为 <code>nil</code>,我们只需要简单的把值从<code>UserDefaults.standard</code> 移除,检查一个基本类型是否为 <code>nil</code> 有点棘手,不过 用协议扩展 <code>Optional</code> 是一个解决方法:

如果我们的值符合 <code>NSCoding</code>,我们就需要使用 <code>NSKeyedArchiver</code> 来把它转换成 <code>Data</code>,然后保存它。

除此之外,我们只需把值直接存储到 <code>UserDefaults</code> 中。

现在,如果我们想要获得 <code>UserDefaults</code> 的支持,我们要做的仅仅是使用新的<code>UserDefaultsState</code> 类!

我们的 <code>UserDefaultsState</code> 会在其值更改时自动更新它的后台存储。在应用启动的时候,它会自动把 <code>UserDefaultsState</code> 中的现有值投入使用。

这只是使用状态容器的例子之一,<code>State</code> 如何扩展到智能地存储自己的数据。在我的项目中,也建立了一些子类,当发生更改时,它们将异步地保留到磁盘或钥匙串。你甚至可以通过使用不同的子类来触发与远程服务器的同步或者将指定标记录到分析库中。它毫无限制。

所以这些状态容器放在哪里呢?通常我把他们静态储存到一个 <code>struct</code> 里,这样可以在整个应用里访问。这与基于 Flux 库存储全局应用状态有些相似。

你可以使用分离或嵌入式的结构体以及不同的访问级别来调整状态容器的作用域。

在状态容器上管理状态有很多好处。以前放在单例上的数据,或在网络代理中传播的数据,现在已经在高层次上浮现出来并且可见。应用程序行为中的所有输入都突然变得清晰可见并且组织严谨。

从 API 响应到特征切换到受保护的钥匙串项,使用状态容器模式是围绕关键信息定义结构的优秀方式。状态容器可以轻松地用于缓存,用户偏好,分析以及应用程序启动之间需要保持的任何事情。

状态容器模式让 UI 组件不用担心如何以及何时生成数据,并开始把焦点转向如何把数据转换成梦幻般的用户体验。

<b>原文发布时间为:2017年8月18日</b>

<b>本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。</b>

继续阅读