Bond, Swift Bond
更新:Bond 7已发布! 查看文档中的迁移指南以了解更多有关更新的信息。
Bond是一个将绑定概念提升到全新水平的Swift绑定框架。它简单、强大、类型安全且多范式——就像Swift。
Bond建立于ReactiveKit之上,弥合了响应式和命令式范式之间的差距。您可以将其作为一个独立的框架使用,通过绑定和响应式数据源简化您状态的变化,也可以与ReactiveKit一起使用,以绑定、响应式代理和响应式数据源来补充您的响应式数据流。
Bond是Binder Architecture的基石——这是与框架一起使用时首选的架构。
为什么使用Bond?
如果你希望在文本框文本变化时执行某些操作。你可以通过设置对象之间的目标-动作机制,并通过所有目标-动作选择器注册的痛苦,或者你只需使用Bond来完成这个操作
textField.reactive.text.observeNext { text in
print(text)
}
现在,您可以将已自动打印的用户输入进行绑定到标签
textField.reactive.text.bind(to: label.reactive.text)
由于绑定到标签文本属性非常普遍,您甚至可以这样做
textField.reactive.text.bind(to: label)
这行代码在文本框的文本属性和标签的文本属性之间建立了一个绑定。实际上,只要用户更改文本框的内容,这些更改就会自动传播到标签。
很多时候,直接绑定是不够的。通常你需要以某种方式转换输入,比如在名字前加上问候语。由于 Bond 由 ReactiveKit 支持,它对函数式范例充满信心。
textField.reactive.text
.map { "Hi " + $0 }
.bind(to: label)
每当文本字段中出现更改时,新值将通过闭包转换并传播到标签。
注意我们是如何使用了文本字段的 reactive.text
属性。它是 Bond 框架提供的 text
属性的可观测表示。对于各种 UIKit 组件,有其他许多类似这种的扩展。它们都放在 .reactive
代理中。
例如,要观察按钮事件做
button.reactive.controlEvents(.touchUpInside)
.observeNext { e in
print("Button tapped.")
}
处理 touchUpInside
事件非常频繁,因此 Bond 提供了仅针对该事件的扩展
button.reactive.tap
.observeNext {
print("Button tapped.")
}
您可以使用任何 ReactiveKit 运算符来转换或组合信号。以下代码片段展示了如何将两个文本字段的值缩减为一个布尔值并应用于按钮的启用属性。
combineLatest(emailField.reactive.text, passField.reactive.text) { email, pass in
return email.length > 0 && pass.length > 0
}
.bind(to: button.reactive.isEnabled)
每当用户在任何这些文本字段中键入内容时,表达式将进行评估并更新按钮状态。
然而,Bond 的强大之处并不仅仅在于耦合各种 UI 组件,而在于将业务逻辑层(即服务或视图模型)与视图层以及相反的绑定。以下是如何将模型中用户关注者数量的属性绑定到标签的示例。
viewModel.numberOfFollowers
.map { "\($0)" }
.bind(to: label)
重点不在于将值赋给标签的文本属性的简单性,而在于创建一个绑定,当关注者数量改变时自动更新标签的文本属性。
Bond 也支持双向绑定。以下是一个如何保持用户名文本字段和视图模型的用户名属性同步的示例(当其中一个更改时,另一个也会更新)
viewModel.username
.bidirectionalBind(to: usernameTextField.reactive.text)
Bond 也很擅长观察各种不同的事件和异步任务。例如,您可以观察一个类似这样的通知
NotificationCenter.default.reactive.notification("MyNotification")
.observeNext { notification in
print("Got \(notification)")
}
.dispose(in: bag)
让我给您举最后一个例子。假设您有一个想要在集合视图中显示的仓库数组。对于每个仓库,您都有一个名称和所有者的个人资料照片。当然,照片不是立即可用的,因为需要下载,但是一旦下载,您希望它在集合视图的单元格中出现。此外,当用户“下拉刷新”并且您的数组获得新的仓库时,您还想将这些添加到集合视图中。
那么您如何进行呢?好吧,与其实现数据源对象,观察照片下载使用 KVO 并手动用新项目更新集合视图,不如使用 Bond 只需几行代码就能做到这一切
repositories.bind(to: collectionView) { array, indexPath, collectionView in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! RepositoryCell
let repository = array[indexPath.item]
repository.name
.bind(to: cell.nameLabel)
.dispose(in: cell.onReuseBag)
repository.photo
.bind(to: cell.avatarImageView)
.dispose(in: cell.onReuseBag)
return cell
}
是的,没错!
响应式扩展
Bond 就是关于绑定和其他响应式扩展。要了解绑定如何工作以及如何创建您自己的绑定,请查看 关于绑定的文档。
如果您对支持哪些绑定和扩展感兴趣,只需在任何 UIKit 或 AppKit 对象上键入 .reactive.
,即可获得可用扩展的列表。您还可以浏览 源文件 以获得概述。
可观察集合
当处理数组时,我们通常需要知道数组是如何变化的。新元素可能已插入数组,旧元素可能已删除或更新。Bond提供了观察此类细粒度变化的机制。
例如,Bond为您提供了一个(Mutable)ObservableArray
类型,可用于生成和观察细粒度变化。
let names = MutableObservableArray(["Steve", "Tim"])
...
names.observeNext { e in
print("array: \(e.collection), diff: \(e.diff), patch: \(e.patch)")
}
您可以使用可观察数组的方式与封装的数组一样。
names.append("John") // prints: array: ["Steve", "Tim", "John"], diff: Inserts: [2], patch: [I(John, at: 2)]
names.removeLast() // prints: array: ["Steve", "Tim"], diff: Deletes: [2], patch: [D(at: 2)]
names[1] = "Mark" // prints: array: ["Steve", "Mark"], diff: Updates: [1], patch: [U(at: 1, newElement: Mark)]
请查看observer collections documentation
了解有关可观察集合的更多信息。
数据源信号
可观察集合和其他数据源信号使我们能够构建强大的UI绑定。例如,可观察数组可以被绑定到集合视图,如下所示
names.bind(to: collectionView, cellType: UserCell.self) { (cell, name) in
cell.titleLabel.text = name
}
无需实现数据源对象并手动完成所有事情。查看数据源信号文档以了解更多信息,以及表格或集合视图绑定。
协议代理
Bond提供了扩展NSObject
,使其轻松将委托方法调用转换为信号。这些扩展建立在ObjC运行时之上,并允许您拦截委托方法调用并将它们转换为信号事件。
Bond使用协议代理来实现表格和集合视图绑定,并提供如tableView.reactive.selectedRowIndexPath
之类的信号。请查看协议代理文档以了解更多信息。
社区扩展
请务必查看Extensions
目录。它包含使Bond易于与如Realm等其他框架和库一起使用的扩展。
如果您有一个扩展能让您喜欢的框架与Bond兼容,并且愿意与大家分享,我们将非常乐意接受您的PR。
需求
- iOS 8.0+ / macOS 10.11+ / tvOS 9.0+
- Swift 4.2
沟通
- 如果您想提问,请开启一个issue。
- 如果您发现了bug,请开启一个issue。
- 如果您有功能请求,请开启一个issue。
- 如果您想贡献,提交一个pull request(包括单元测试)。
安装
使用Carthage
-
将以下内容添加到您的Cartfile中
github "DeclarativeHub/Bond"
-
运行
carthage update
-
按照Carthage Readme中描述的方式添加框架
使用Accio
-
将以下内容添加到您的Package.swift
.package(url: "https://github.com/DeclarativeHub/Bond.git", .upToNextMajor(from: "7.4.1")),
-
接下来,像这样将
Bond
添加到您的App targets依赖中.target( name: "App", dependencies: [ "Bond", ] ),
-
然后运行
accio update
。
使用CocoaPods
-
将以下内容添加到您的Podfile中
pod 'Bond'
-
运行
pod install
。
许可
MIT 许可协议 (MIT)
版权所有 © 2015-2019 Srdan Rasic (@srdanrasic)
在此特此授予任何获得本软件及其相关文档文件(以下简称“软件”)副本的个人,免费、无限制地使用软件的权利,包括但不仅限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件副本,并允许提供软件的个人为此目的使用软件,受以下条件的约束
版权声明和本许可声明应包含在软件所有副本或实质部分的副本中。
软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、适用于特定目的和不受侵权等保证。在任何情况下,作者或版权持有人均不对任何因使用或与软件或软件的使用或其他处理方式有关的索赔、损害或其他责任承担责任。