SimpleStates 0.1.3

SimpleStates 0.1.3

Bryce Dougherty 维护。



  • Bryce Dougherty

SimpleStates

Version License Platform

Swift 中用于响应式编程的简单状态

允许您将 “状态” 附带到 Swift 中给定的属性,并且当状态改变时,该属性也会改变。

与其他库(如 ReactiveSwift)相比,它更加轻量级,且更容易理解(尽管如果您需要进行任何重量级操作,您可能需要使用那些库)。

安装

SimpleStates 可以通过 CocoaPods 获取。要安装它,请简单地添加以下行到您的 Podfile 中

pod 'SimpleStates'

使用方法

基本用法

import SimpleStates
let myState = State("hello")

let label = UILabel(CGRect(x: 0, y: 0, width: 100, height: 20))

var changingString = "initial"

myState.bind(label, .\text)
// label.text is now "hello"

myState.on({ newValue in 
	changingString = newValue
})
// changingString is now "hello"

myState.set("hello world")
// label.text and changingString are both now "hello world"

状态类型

提供了 3 种状态类型

普通状态

保存一个值,当用户修改此值时更新观察者。

构造方式如下:

let myState = State(defaultValue) 

或者,如果您不想依赖编译器的推断,可以显式设置状态的类型。

let myState = State<CGFloat>(1.0) // would normally be Float

通知中心状态

当发出通知时更新观察者。

这些可以通过两种方式构造。

第一种方式需要三个参数:要监听的通知、状态的初始值,以及一个闭包,该闭包从通知中转换Notification参数并将返回的值设置为状态的值。

let isPortrait = NotificationState(
  UIDevice.orientationDidChangeNotification, 
  default: "",
	mutator: { notif in 
  	return notif.description
  }
)

如果您不需要通知参数的值,可以通过只传递要监听的通知和一个属性获取器来构造状态。

let isPortrait = NotificationState(
  UIDevice.orientationDidChangeNotification, 
  getter: UIDevice.current.orientation
)

这将更新状态的值,在接收到通知时始终为UIDevice.current.orientation。

键值观察状态

观察给定遵守KVO协议的属性,并在它更改时更新状态。

这是使用对象和一个属性KeyPath初始化的,如下所示

let myState = KVOState(obj: myObject, keyPath: \.propertyName)

通常,KVO状态的属性值是不可变的,以保持与所跟踪属性的连贯性。如果跟踪的属性是可变的,并且您想更改它,则可以使用MutableKVOState,然后使用.setValue来设置支持变量的值。

let myState = MutableKVOState(obj: myObject, keyPath: \.propertyName)

通常,您可以使用myObject.propertyName = newValue来实现相同的效果,但这将允许您更新状态,即使您没有这些引用。

注意

为了避免内存泄漏,KVOState不会保留其绑定对象的引用,因此您必须确保您自己不要释放它。

绑定状态

可以绑定一个状态的类属性,使它在更新时更新该属性。

这可以通过几种方式完成:

let label = UILabel()

/// with a KeyPath
myState.bind(label, .\text)

// or with a BundledKeyPath (more on that later)
myState(label..\.text)

// or with some nice helper functions
label..\.text <-> myState

/// These all do the exact same thing

第一个选项是一个函数,它接受两个参数:一个对象和一个指向您想要绑定的属性的KeyPath。很简单。

第二个选项与第一个相似,但是在State对象本身上调用的。这里我使用的是BundleKeyPath,它包含我们指向的对象和路径本身,但这两个都接受两种类型的参数。

最后一个选项是几个辅助函数的组合。

..:它接受一个对象和一个KeyPath,将它们组合成一个名为BundledKeyPath的单个对象,然后可以将其绑定到状态。如果您对象中不存在指定的KeyPath,它将抛出一个错误。

<->:它将给定的BundledKeyPath绑定到State

设置绑定时,属性会立即更新为状态的值,并且每当状态的值更新时都会更新。

更新状态

要更新vanilla状态的值,请使用State.set方法

myState.set("New Text")

label.text已经更新为我们新的值(并且UIKit将自动更新)

注意

KVOState和NotificationState会自动更新,无法手动设置

获取状态的当前值

要获取当前值而不附加监听器,只需调用State.get()

let myState = State("Text")

myState.get()
/// returns "Text"

高级状态绑定

有时,简单的属性绑定就不够了。例如,如果您需要首先修改值,或者如果您需要调用单独的更新函数,比如使用UITableView

在这种情况下,您不使用State.bind(BundledKeyPath)State.bind(Any, KeyPath),而是使用State.on((newValue)->Void),这允许您传递一个自定义闭包,在值更新时调用。

此示例将在更新之前将笑脸添加到标签文本的前面

let myState = State("Text")
let label = UILabel()

myState.on({newValue in 
     label.text = val + " 😀"
})
myState.set("hello world")

/// label.text is now "hello world 😀"

对于表格视图之类的用法,例如,您可以在闭包内通知视图从内部重新加载,然后在实际的渲染方法中调用 .get()。每当您调用 dataSource.set 时,tableView 都将重新渲染。

let dataSource = State<[String]>([])

override func viewDidLoad() {
	super.viewDidLoad()
	dataSource.on({ _ in 
  	self.tableView.reloadData()
  })
}


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
	cell.titleLabel.text = dataSource.get()[indexPath.row]
  // Configure the cell...

  return cell
}

当然,您可以在普通属性上使用 didSet 来做这个特定的操作,但这是一个例子🙂

注意

State.on闭包总是在主队列上调用,所以您无需担心在它们内部进行UI更新

从状态中解绑属性

每次创建绑定时,都会返回一个 Binding,允许您稍后使用 State.unBind 解绑

let myState = State("Text")
let label = UILabel()

let labelBinding = myState.bind(label, \.text)
/// OR 
let labelBinding = label..\.text <-> myState

myState.set("Hello World")
/// label.text is now "Hello World"
myState.unBind(labelBinding)
myState.set("Goodbye World")
/// label.text is still "Hello World"

其他方法

您可以直接使用默认绑定创建一个状态,但这将不允许您将来解绑它。

let myLabel = UILabel()

let myState = boundState(myLabel..\.text, "Text")

/// myLabel.text is now "Text"

myState.set("New Text")

/// myLabel.text is now "New Text"

您还可以使用类似于React的state hook的语法创建一个状态对象

let (myState, getMyState, setMyState) = useState("Text")

这返回一个包含状态对象、获取器函数(该函数不会添加监听器)和设置器函数的元组。

可以使用如下方式使用它

let (myState, getMyState, setMyState) = useState("Text")

let myLabel = UILabel()

myLabel..\.text <-> myState

setMyState("New Text")
/// myLabel.text is now "New Text"

getMyState()
/// returns "New Text"

捆绑键路径

为简化创建绑定时的操作,存在一个结构体 BundledKeyPath,它专门用于存储一个键路径及其所在的对象的引用。

要创建一个这样的实例,您可以直接调用构造函数,或者使用 .. 函数。

let myLabel = UILabel()

let bkp = BundledKeyPath(myLabel, \.text)
// OR
let bkp = myLabel..\.text

这样做只是使使用给定中缀函数构建绑定变得更容易,因为我们需要对对象和键路径的引用来实际设置给定的属性,而Swift不允许我们创建自己的三元运算符。

当构建一个 MutableKVOState 时,您也可以使用这些功能。

let state = MutableKVOState(myClass..\.myKVOConformingProperty)

注解

捆绑键路径始终可变,因为不可变属性不能绑定到状态(显然的原因)。它们不能通过只读属性创建。


就是这样! 如果有任何问题,请告诉我!

作者

Bryce Dougherty – [email protected]

许可

本项目受MIT许可提供