Utilisation de KVO dans une cellule tableview pour suivre les modifications apscopes aux propriétés spécifiques d'une instance de class dans Swift 3

J'essaie d'utiliser KVO pour suivre les modifications apscopes aux propriétés de mon object Account, dont la partie importante ressemble à ceci:

class Account: NSObject { var battleTagLabel: Ssortingng! dynamic var onlineStatusIcon: Ssortingng! dynamic var currentGameIcon: Ssortingng! dynamic var currentStatusLabel: Ssortingng! 

Je veux être averti dans ma cellule tableview lorsque ces trois propriétés changent de valeur. Ma class de cellules viewview:

 import Foundation import UIKit private var observerContext = 0 class FriendAccountCell: UITableViewCell { @IBOutlet weak var onlineStatusIcon: UIImageView! @IBOutlet weak var battleTag: UILabel! @IBOutlet weak var currentGameIcon: UIImageView! @IBOutlet weak var currentStatusLabel: UILabel! weak var tableView: UITableView! weak var delegate: CustomCellDelegate? var onlineStatusIconFlag = false var currentStatusLabelFlag = false var currentGameIconFlag = false var account: Account? { willSet { if onlineStatusIconFlag { print("onlineStatusIconFlag: \(onlineStatusIconFlag)") if newValue?.onlineStatusIcon != account?.onlineStatusIcon && account?.onlineStatusIcon != nil { self.account?.removeObserver(self, forKeyPath: #keyPath(onlineStatusIcon)) onlineStatusIconFlag = false } } if currentStatusLabelFlag { if newValue?.currentStatusLabel != account?.currentStatusLabel && account?.currentStatusLabel != nil { account?.removeObserver(self, forKeyPath: #keyPath(currentStatusLabel)) currentStatusLabelFlag = false } } if currentGameIconFlag { if newValue?.currentGameIcon != account?.currentGameIcon && account?.currentGameIcon != nil { account?.removeObserver(self, forKeyPath: #keyPath(currentGameIcon)) currentGameIconFlag = false } } } didSet { if oldValue?.onlineStatusIcon != account?.onlineStatusIcon { if account?.onlineStatusIcon == "onine" { self.onlineStatusIcon.image = UIImage(named: "20pxButtonGreen") } else if account?.onlineStatusIcon == "idle" { self.onlineStatusIcon.image = UIImage(named: "20pxButtonYellow") } else if account?.onlineStatusIcon == "busy" { self.onlineStatusIcon.image = UIImage(named: "20pxButtonRed") } else { self.onlineStatusIcon.image = UIImage(named: "20pxButtonBlack") } account?.addObserver(self, forKeyPath: #keyPath(onlineStatusIcon), context: &observerContext) onlineStatusIconFlag = true } if oldValue?.currentStatusLabel != account?.currentStatusLabel { self.currentStatusLabel?.text = account?.currentStatusLabel account?.addObserver(self, forKeyPath: #keyPath(currentStatusLabel), context: &observerContext) currentStatusLabelFlag = true } if oldValue?.currentGameIcon != account?.currentGameIcon { if let currentGame = account?.currentGameIcon { switch currentGame { case "overwatch": self.currentGameIcon.image = UIImage(named: "logo-ow") case "hearthstone": self.currentGameIcon.image = UIImage(named: "logo-hs") case "worldOfWarcraft": self.currentGameIcon.image = UIImage(named: "logo-wow") case "diablo3": self.currentGameIcon.image = UIImage(named: "logo-d3") case "heroesOfTheStorm": self.currentGameIcon.image = UIImage(named: "logo-heroes") case "starCraft": self.currentGameIcon.image = UIImage(named: "logo-sc") case "starCraft2": self.currentGameIcon.image = UIImage(named: "logo-sc2") case "": self.currentGameIcon.image = nil default: self.currentGameIcon.image = nil } } account?.addObserver(self, forKeyPath: #keyPath(currentGameIcon), context: &observerContext) currentGameIconFlag = true } } } override func observeValue(forKeyPath keyPath: Ssortingng?, 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(cell: self) } deinit { print("deinit called") if onlineStatusIconFlag { account?.removeObserver(self, forKeyPath: #keyPath(onlineStatusIcon)) onlineStatusIconFlag = false } if currentStatusLabelFlag { account?.removeObserver(self, forKeyPath: #keyPath(currentStatusLabel)) currentStatusLabelFlag = false } if currentGameIconFlag { account?.removeObserver(self, forKeyPath: #keyPath(currentGameIcon)) currentGameIconFlag = false } } 

Et voici la section pertinente de ma class de tableview:

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let identifier: Ssortingng = "FriendAccountCell" var cell: FriendAccountCell if let friendCell = self.tableView.dequeueReusableCell(withIdentifier: identifier){ cell = friendCell as! FriendAccountCell } else { cell = FriendAccountCell(style: .default, reuseIdentifier: identifier) cell.selectionStyle = .none } var filteredFriends = orderFriends(friendsArray: Array(MyAccountInfo.allFriends.values)) cell.delegate = self cell.account = filteredFriends[indexPath.row] cell.battleTag.text = filteredFriends[indexPath.row].battleTagLabel cell.currentStatusLabel.text = filteredFriends[indexPath.row].currentStatusLabel return cell } 

(Ce n'est pas collé ci-dessus, mais j'implémente aussi la fonction delegate dans ma class tableview pour recharger les cellules spécifiques.)

Les modifications apscopes à ces propriétés spécifiques se produisent rapidement lorsque l'application est chargée pour la première fois et que toutes datatables les plus récentes sont récupérées sur le server. Ensuite, les changements se produisent plus régulièrement et lentement.

Malgré les drapeaux et les autres stratégies que j'ai essayé de suivre correctement l'ajout et le retrait des observateurs, je reçois toujours l'erreur "Impossible de supprimer l'observateur pour la key car elle n'est pas enregistrée en tant qu'observateur".

Je suggère de simplifier la logique d'ajout / suppression d'observateur. Le code actuel est trop compliqué et offre trop de paths où vous pourriez manquer l'un ou l'autre. Donc, supprimez simplement les observateurs dans willSet et ajoutez des observateurs dans didSet :

 var account: Account? { willSet { account?.removeObserver(self, forKeyPath: #keyPath(Account.onlineStatusIcon)) account?.removeObserver(self, forKeyPath: #keyPath(Account.currentStatusLabel)) account?.removeObserver(self, forKeyPath: #keyPath(Account.currentGameIcon)) } didSet { account?.addObserver(self, forKeyPath: #keyPath(Account.onlineStatusIcon), context: &observerContext) account?.addObserver(self, forKeyPath: #keyPath(Account.currentStatusLabel), context: &observerContext) account?.addObserver(self, forKeyPath: #keyPath(Account.currentGameIcon), context: &observerContext) // do any additional logic here you want here } } deinit { account?.removeObserver(self, forKeyPath: #keyPath(Account.onlineStatusIcon)) account?.removeObserver(self, forKeyPath: #keyPath(Account.currentStatusLabel)) account?.removeObserver(self, forKeyPath: #keyPath(Account.currentGameIcon)) } 

De plus, si vous définissez un account dans init , callbackez-vous que willSet n'est pas appelé alors, vous devrez donc append manuellement les observateurs dans cette situation.