Чтобы использовать 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 и прошивкой 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 механизм наблюдателя, потому что т он не должен знать (и не должен) ничего о наблюдателе. Просто нужно указать, что он поддерживает динамическую отправку, и все.
Для ознакомления с Свифт 2, см. previous revision of this answer.
Спасибо, Роб. Мне очень понравился ваш подход. – mra214
Что такое & observerContext в этом случае и откуда оно взялось? – Eden
@Eden - 'observValue (forKeyPath: of: change: context:)' нуждается в некотором «контексте», чтобы узнать, наблюдает ли это уведомление или что-то, что его класс 'super' наблюдает. Поэтому мы указываем уникальный «контекст», используемый при создании наблюдателя, и проверяем при обработке уведомления. Стандартный способ сделать это - определить некоторую переменную, в данном случае 'observerContext', и использовать ее адрес памяти в качестве этого уникального идентификатора. Это обычная картина, но я извиняюсь, если это не очевидно. Я добавил его выше (а также предлагаю упрощенную версию SWIFT 4, iOS 11). – Rob