如何用 Swift 和 KVO 观察单个数组元素的变化(更新)?
How to observe individual array element changes (update) with Swift and KVO?
我需要 subscribe/unsubscribe 数组的各个元素吗?
我想单独更新每个 table 视图单元格以反映支持数组中的更改。我所说的更改不是 append/remove 操作,而是更新数组对象的属性。
我希望我能够解释我想要实现的目标。
谢谢
您可以使用委托来处理单个 cell:s 数据(例如数组的元素)与这些单元格的所有者(即数组的所有者)之间的通信。 "backing array" 中的更改可以传播到使用具有适当信息的委托回调所拥有的数组。
例如:
/* delegate protocol blueprinting delegate callback methods */
protocol MyDelegate: class {
func arrayEntryUpdated(element: Foo)
}
/* lets assume your table view cells data source are Foo objects */
class Foo {
var id: Int = -1
private var bar : String = "" {
didSet {
/* notify the delegate each time the bar
property of a Foo object is set */
delegate?.arrayEntryUpdated(self)
}
}
weak var delegate: MyDelegate?
}
/* you can then hande individual updates of the Foo objects via
delegate callbacks to the owner of the Foo array */
class MyArrayOwningClass: MyDelegate {
var fooArr : [Foo]
init(fooArr: [Foo]) {
self.fooArr = fooArr
self.fooArr.enumerate().forEach { .id = [=10=]; .delegate = self }
}
// MyDelegate
func arrayEntryUpdated(element: Foo) {
print("Foo element of id #\(element.id) updated.")
// ... in your case, handle individual cell updating
}
}
用法示例:
let fooArr = [Foo(), Foo(), Foo()] // "data source"
let owner = MyArrayOwningClass(fooArr: fooArr)
// update an element of the data source
fooArr[1].bar = "bar" // via delegate: "Foo element of id #1 updated."
您可以对后备数组中存储的变量使用 setter 观察器。
var s = "myString" {
willSet {
// do the update on the cell here with newValue
}
didSet {
// do something with oldValue
}
}
var array: [String] = []
array.append(s)
当您更改 array
中的值时,将执行 willSet 和 didSet,您可以在单元格上调用一个函数来执行您想要的更新。
要使用 KVO,请使用 dynamic
属性声明模型对象:
class Foo: NSObject {
@objc dynamic var bar: String // in Swift 3, `@objc` is not necessary; in Swift 4 we must make this explicit
init(bar: String) {
self.bar = bar
super.init()
}
}
然后,让cell处理KVO。首先,我有一个协议,单元可以通过该协议通知 table 视图它需要重新加载:
protocol CustomCellDelegate: class {
func didUpdateObject(for cell: UITableViewCell)
}
并且 table 视图控制器可以遵守此 CustomCellDelegate
协议并在通知需要时重新加载单元格:
func didUpdateObject(for cell: UITableViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
tableView.reloadRows(at: [indexPath], with: .fade)
}
}
所以,然后定义单元格来设置和处理KVO。在 Swift 4 和 iOS 11 中,您可以使用基于闭包的 observe
方法和新的强类型键:
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
private var token: NSKeyValueObservation?
var object: Foo? {
willSet {
token?.invalidate()
}
didSet {
textLabel?.text = object?.bar
token = object?.observe(\.bar) { [weak self] object, change in
if let cell = self {
cell.delegate?.didUpdateObject(for: cell)
}
}
}
}
}
在Swift 3:
class CustomCell: UITableViewCell {
private var observerContext = 0
weak var delegate: CustomCellDelegate?
var object: Foo? {
willSet {
object?.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
}
didSet {
textLabel?.text = object?.bar
object?.addObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
delegate?.didUpdateObject(for: self)
}
deinit {
object?.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
}
}
现在 cellForRowAtIndexPath
可以设置 delegate
和 object
属性:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
cell.delegate = self
cell.object = objects![indexPath.row]
return cell
}
在我看来,这种 KVO 机制优于 Swift 观察者机制,因为模型对象不需要(也不应该)知道关于观察者的任何信息。只需要说明它支持动态调度就可以了。
对于上述的 Swift 2 版本,请参阅 previous revision of this answer。
我需要 subscribe/unsubscribe 数组的各个元素吗?
我想单独更新每个 table 视图单元格以反映支持数组中的更改。我所说的更改不是 append/remove 操作,而是更新数组对象的属性。 我希望我能够解释我想要实现的目标。 谢谢
您可以使用委托来处理单个 cell:s 数据(例如数组的元素)与这些单元格的所有者(即数组的所有者)之间的通信。 "backing array" 中的更改可以传播到使用具有适当信息的委托回调所拥有的数组。
例如:
/* delegate protocol blueprinting delegate callback methods */
protocol MyDelegate: class {
func arrayEntryUpdated(element: Foo)
}
/* lets assume your table view cells data source are Foo objects */
class Foo {
var id: Int = -1
private var bar : String = "" {
didSet {
/* notify the delegate each time the bar
property of a Foo object is set */
delegate?.arrayEntryUpdated(self)
}
}
weak var delegate: MyDelegate?
}
/* you can then hande individual updates of the Foo objects via
delegate callbacks to the owner of the Foo array */
class MyArrayOwningClass: MyDelegate {
var fooArr : [Foo]
init(fooArr: [Foo]) {
self.fooArr = fooArr
self.fooArr.enumerate().forEach { .id = [=10=]; .delegate = self }
}
// MyDelegate
func arrayEntryUpdated(element: Foo) {
print("Foo element of id #\(element.id) updated.")
// ... in your case, handle individual cell updating
}
}
用法示例:
let fooArr = [Foo(), Foo(), Foo()] // "data source"
let owner = MyArrayOwningClass(fooArr: fooArr)
// update an element of the data source
fooArr[1].bar = "bar" // via delegate: "Foo element of id #1 updated."
您可以对后备数组中存储的变量使用 setter 观察器。
var s = "myString" {
willSet {
// do the update on the cell here with newValue
}
didSet {
// do something with oldValue
}
}
var array: [String] = []
array.append(s)
当您更改 array
中的值时,将执行 willSet 和 didSet,您可以在单元格上调用一个函数来执行您想要的更新。
要使用 KVO,请使用 dynamic
属性声明模型对象:
class Foo: NSObject {
@objc dynamic var bar: String // in Swift 3, `@objc` is not necessary; in Swift 4 we must make this explicit
init(bar: String) {
self.bar = bar
super.init()
}
}
然后,让cell处理KVO。首先,我有一个协议,单元可以通过该协议通知 table 视图它需要重新加载:
protocol CustomCellDelegate: class {
func didUpdateObject(for cell: UITableViewCell)
}
并且 table 视图控制器可以遵守此 CustomCellDelegate
协议并在通知需要时重新加载单元格:
func didUpdateObject(for cell: UITableViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
tableView.reloadRows(at: [indexPath], with: .fade)
}
}
所以,然后定义单元格来设置和处理KVO。在 Swift 4 和 iOS 11 中,您可以使用基于闭包的 observe
方法和新的强类型键:
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
private var token: NSKeyValueObservation?
var object: Foo? {
willSet {
token?.invalidate()
}
didSet {
textLabel?.text = object?.bar
token = object?.observe(\.bar) { [weak self] object, change in
if let cell = self {
cell.delegate?.didUpdateObject(for: cell)
}
}
}
}
}
在Swift 3:
class CustomCell: UITableViewCell {
private var observerContext = 0
weak var delegate: CustomCellDelegate?
var object: Foo? {
willSet {
object?.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
}
didSet {
textLabel?.text = object?.bar
object?.addObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
delegate?.didUpdateObject(for: self)
}
deinit {
object?.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
}
}
现在 cellForRowAtIndexPath
可以设置 delegate
和 object
属性:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
cell.delegate = self
cell.object = objects![indexPath.row]
return cell
}
在我看来,这种 KVO 机制优于 Swift 观察者机制,因为模型对象不需要(也不应该)知道关于观察者的任何信息。只需要说明它支持动态调度就可以了。
对于上述的 Swift 2 版本,请参阅 previous revision of this answer。