如何使用Swift和KVO观察单个数组元素的更改(更新)?

时间:2016-05-29 08:26:56

标签: ios swift key-value-observing

我是否需要订阅/取消订阅数组的各个元素?

我想单独更新每个表格视图单元格以反映支持数组中的更改。通过更改,我的意思是不追加/删除操作,而是更新数组对象的属性。 我希望我能够解释我想要实现的目标。 谢谢

3 个答案:

答案 0 :(得分:8)

要使用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()
    }
}

然后,让细胞处理KVO。首先,我有一个协议,通过该协议,单元格可以通知表视图需要重新加载:

protocol CustomCellDelegate: class {
    func didUpdateObject(for cell: UITableViewCell)
}

表视图控制器可以符合此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可以设置delegateobject属性:

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

答案 1 :(得分:2)

您可以对后备数组中存储的变量使用setter observer。

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,并且您可以在执行所需更新的单元格上调用函数。

答案 2 :(得分:1)

您可以使用委托处理单个单元格的数据(例如,数组的元素)与这些单元格的所有者(即数组的所有者)之间的通信。 "支持阵列的变化"可以使用具有适当信息的委托回调传播到拥有的数组。

E.g:

/* 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 { $1.id = $0; $1.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."