Bond,Swift Bond

Platform CI Status Twitter


更新:Bond 7 已发布! 查看迁移指南,了解有关此更新的更多信息。

Bond 是一个 Swift 绑定框架,它将绑定概念提升到一个全新的水平。它像 Swift 一样,简单、强大、类型安全且支持多种范式。

Bond 构建于 ReactiveKit 之上,弥合了响应式和命令式范式之间的差距。您可以将其用作独立框架,通过绑定和响应式数据源来简化您的状态更改,也可以将其与 ReactiveKit 一起使用,通过绑定、响应式委托和响应式数据源来补充您的响应式数据流。

Bond 是 Binder 架构的支柱——该架构是使用此框架的首选架构。

为什么要使用 Bond?

假设您想在文本字段的文本更改时执行某些操作。嗯,您可以设置对象之间的target-action机制,并经历所有 target-action 选择器注册的痛苦,或者您可以简单地使用 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 运算符来转换或组合信号。以下代码片段描述了如何将两个文本字段的值缩减为布尔值并应用于按钮的 enabled 属性。

combineLatest(emailField.reactive.text, passField.reactive.text) { email, pass in
    return email.length > 0 && pass.length > 0
  }
  .bind(to: button.reactive.isEnabled)

每当用户在任何一个文本字段中输入内容时,表达式将被评估,按钮状态将被更新。

然而,Bond 的强大之处不在于耦合各种 UI 组件,而在于将业务逻辑层(即 Service 或 View Model)绑定到 View 层,反之亦然。以下是如何将模型的用户粉丝数属性绑定到标签的示例。

viewModel.numberOfFollowers
  .map { "\($0)" }
  .bind(to: label)

这里的重点不是将值简单地赋值给标签的文本属性,而是创建绑定,该绑定在粉丝数更改时自动更新标签的文本属性。

Bond 还支持双向绑定。这是一个示例,说明如何使用户名文本字段和 View Model 的 username 属性保持同步(无论哪个更改,另一个也会更新)

viewModel.username
  .bidirectionalBind(to: usernameTextField.reactive.text)

Bond 也非常适合观察各种不同的事件和异步任务。例如,您可以像这样观察通知

NotificationCenter.default.reactive.notification("MyNotification")
  .observeNext { notification in
    print("Got \(notification)")
  }
  .dispose(in: bag)

让我给您最后一个例子。假设您有一个想要在集合视图中显示的存储库数组。对于每个存储库,您都有一个名称及其所有者的个人资料照片。当然,照片不会立即可用,因为它必须下载,但是一旦您获得它,您就希望它出现在集合视图的单元格中。此外,当用户执行“下拉刷新”并且您的数组获得新的存储库时,您也希望这些存储库出现在集合视图中。

那么您如何进行呢?嗯,使用 Bond,您可以仅用几行代码完成所有这些操作,而不是实现数据源对象、使用 KVO 观察照片下载以及手动使用新项目更新集合视图

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)]

查看可观察集合文档,以了解有关可观察集合的更多信息。

数据源信号

可观察集合和其他数据源信号使我们能够构建强大的 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。

要求

沟通

安装

Carthage

  1. 将以下内容添加到您的 Cartfile

    github "DeclarativeHub/Bond"
    
  2. 运行 carthage update

  3. 按照 Carthage Readme 中的描述添加框架

Accio

  1. 将以下内容添加到您的 Package.swift

    .package(url: "https://github.com/DeclarativeHub/Bond.git", .upToNextMajor(from: "7.4.1")),
  2. 接下来,像这样将 Bond 添加到您的 App targets 依赖项

    .target(
        name: "App",
        dependencies: [
            "Bond",
        ]
    ),
  3. 然后运行 accio update

CocoaPods

  1. 将以下内容添加到您的 Podfile

    pod 'Bond'
    
  2. 运行 pod install

许可证

MIT 许可证 (MIT)

版权所有 (c) 2015-2019 Srdan Rasic (@srdanrasic)

特此授予任何人免费许可,以获取本软件和相关文档文件(“软件”)的副本,并在不受限制的情况下处理本软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件副本的权利,并允许向其提供软件的人员这样做,但须符合以下条件

上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。

本软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于适销性、特定用途的适用性和非侵权性的保证。在任何情况下,作者或版权持有者均不对任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权行为或其他方面,由软件引起、源于软件或与软件或软件的使用或其他交易有关。