Using KVO within a tableview cell to track changes to specific properties of a class instance in Swift 3

生来就可爱ヽ(ⅴ<●) 提交于 2019-12-12 23:29:08

问题


I'm trying to use KVO to track changes to the properties of my Account object, the important part of which looks like this:

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

I want to be notified within my tableview cell when those three properties change in value. My tableview cell class:

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: 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(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
    }
}

And here is the relevant section of my tableview class:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let identifier: String = "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
}

(It's not pasted above, but I also implement the delegate function in my tableview class to reload the specific cells.)

The changes to these specific properties happen quickly when the app first loads and all the most current data gets grabbed from the server. Afterward the changes happen more steadily and slowly.

Despite the flags and other strategies I've tried to properly track the addition and removal of observers, I'm still getting the "Cannot remove observer for the key path because it is not registered as an observer" error.


回答1:


I'd suggest simplifying the add/remove observer logic. The current code is too complicated and offers too many paths where you might miss one or the other. So, just remove observers in willSet, and add observers in 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))
}

Also, if you set account in init, remember that willSet is not called then, so you'll have to manually add the observers yourself in that one situation.



来源:https://stackoverflow.com/questions/45805868/using-kvo-within-a-tableview-cell-to-track-changes-to-specific-properties-of-a-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!