繁体   English   中英

KVO绑定失败迅速

[英]KVO Binding failing in swift

这个错误使我感到非常沮丧。 我正在使用KVO,如下所示:

dynamic var product: NSNumber? {
    get {
        return (defaults.objectForKey("product") != nil ? defaults.integerForKey("product") : nil)
    }
    set(newValue) {
        if newValue != product {
            willChangeValueForKey("product")
            if let value = newValue {
                defaults.setInteger(value.integerValue, forKey: "product")
            } else {
                defaults.removeObjectForKey("product")
            }
            didChangeValueForKey("product")
            reader.product = newValue?.integerValue
            refresh()
        }
    }
}

override class func automaticallyNotifiesObserversForKey(key: String) -> Bool {
    return true
}

另一个对象按如下所示修改属性;

    info.product = NSNumber(short: prodId)

调用时,结果为以下错误;

    Thread 1: EXC_BAD_ACCESS(code=1, address = 0x0)

和堆栈跟踪:

#0  0x37c4cf66 in objc_msgSend ()
#1  0x2acca772 in NSKeyValuePushPendingNotificationPerThread ()
#2  0x2acbae3c in NSKeyValueWillChange ()
#3  0x2aca7c04 in -[NSObject(NSKeyValueObserverNotification) willChangeValueForKey:] ()
#4  0x2acdafc0 in _NSSetObjectValueAndNotify ()
#5  0x002a3c44 in Module.MyView.pickerView (Module.MyView)(ObjectiveC.UIPickerView, didSelectRow : Swift.Int, inComponent : Swift.Int) -> () at /<***>/MyView.swift:122

现在,我已经设置了断点并打印,并确定在调用var setter代码之前已调用NSKeyValueWillChange ,因此看来这与目标C中的使用方式不同,在发送NSKeyValueWillChange的消息之前设置员被调用。 在目标C中,设置者必须手动调用willChangeValueForKey("product") 有人知道发生了什么吗?

附带说明一下,以下是发生崩溃的代码。 错误发生在第三行,我猜测r9为0x0与它有关。

libobjc.A.dylib`objc_msgSend:
0x37c4cf60:  cbz    r0, 0x37c4cf9e            ; objc_msgSend + 62
0x37c4cf62:  ldr.w  r9, [r0]
0x37c4cf66:  ldrh.w r12, [r9, #12]
0x37c4cf6a:  ldr.w  r9, [r9, #8]
0x37c4cf6e:  and.w  r12, r12, r1
0x37c4cf72:  add.w  r9, r9, r12, lsl #3
0x37c4cf76:  ldr.w  r12, [r9]
0x37c4cf7a:  teq.w  r12, r1
0x37c4cf7e:  bne    0x37c4cf86                ; objc_msgSend + 38
0x37c4cf80:  ldr.w  r12, [r9, #4]
0x37c4cf84:  bx     r12
0x37c4cf86:  cmp.w  r12, #0x1
0x37c4cf8a:  blo    0x37c4cf98                ; objc_msgSend + 56
0x37c4cf8c:  it     eq
0x37c4cf8e:  ldreq.w r9, [r9, #4]
0x37c4cf92:  ldr    r12, [r9, #8]!
0x37c4cf96:  b      0x37c4cf7a                ; objc_msgSend + 26
0x37c4cf98:  ldr.w  r9, [r0]
0x37c4cf9c:  b      0x37c4d1e0                ; _objc_msgSend_uncached
0x37c4cf9e:  mov.w  r1, #0x0
0x37c4cfa2:  bx     lr

您必须确保调用didChangeValueForKey:willChangeValueForKey:。 您不需要重写getter和setter即可触发KVO通知。 我不知道具体的实现方式,但是在您的类中,如果信息类必须触发产品更改通知,则它看起来像这样,

 class MyInfoClass {
  var product: NSNumber?{
      willSet{
        self.willChangeValueForKey("product")
      }
      didSet{
        self.didChangeValueForKey("product")
      }
    }

  override class func automaticallyNotifiesObserversForKey(key: String) -> Bool {
    if key == "product"{
      return true
    }else{
      return automaticallyNotifiesObserversForKey(key)
    }
  }
}

但是,如果您希望使用动态方法,则无需自动覆盖kvo方法NotifyObserversForKey ,也无需触发willChangeValueForKey:didChangeValueForKey :。 请注意,您的类应该是NSObject的子类。 在这种情况下,您的课程将看起来像这样,

class MyInfoClass:NSObject{
  dynamic var product: NSNumber?
}

我无法重现您的问题(也许是因为我没有reader对象或refresh()函数),但是您的代码正在为product生成两个(嵌套的)KVN。 如果automaticallyNotifiesObserversForKey返回true (这意味着product KVN将自动发生),则不应手动发布该属性的KVN。

有两种选择:

  1. 您可以关闭product自动KVN并自己进行:

     dynamic var product: NSNumber? { get { return defaults.objectForKey("product") as? NSNumber } set { if newValue != product { willChangeValueForKey("product") if let value = newValue { defaults.setInteger(value.integerValue, forKey: "product") } else { defaults.removeObjectForKey("product") } didChangeValueForKey("product") // reader.product = newValue?.integerValue // refresh() } } } override class func automaticallyNotifiesObserversForKey(key: String) -> Bool { if key == "product" { return false } return super.automaticallyNotifiesObserversForKey(key) // prudent to call super rather than just returning `true` } 
  2. 您可以将自动KVN保持打开状态,然后自己不要执行以下操作:

     dynamic var product: NSNumber? { get { return defaults.objectForKey("product") as? NSNumber } set { if newValue != product { if let value = newValue { defaults.setInteger(value.integerValue, forKey: "product") } else { defaults.removeObjectForKey("product") } // reader.product = newValue?.integerValue // refresh() } } } // we don't need this at all if all we're going to do is to return the super value // // override class func automaticallyNotifiesObserversForKey(key: String) -> Bool { // return super.automaticallyNotifiesObserversForKey(key) // } 

编辑虽然此解决方案最初有效,但后来的更改使其无效。

我只是找到了解决方案,对我来说,这似乎很愚蠢。 我要做的就是像这样移动willChangeValueForKeydidChangeValueForKey方法调用。

dynamic var product: NSNumber? {
    get {
        return (defaults.objectForKey("product") != nil ? defaults.integerForKey("product") : nil)
    }
    set(newValue) {
        willChangeValueForKey("product")
        if newValue != product {
            if let value = newValue {
                defaults.setInteger(value.integerValue, forKey: "product")
            } else {
                defaults.removeObjectForKey("product")
            }
            reader.product = newValue?.integerValue
            refresh()
        }
        didChangeValueForKey("product")
    }
}

猜测,我会说实现很聪明。 如果在mutator方法中没有willChangeValueForKey的调用视为第一件事,则在进入mutator之前先对其进行调用,然后willChangeValueForKey该调用。 移动了这些调用之后,该代码现在不会进行这些默认调用,并且它们都可以正常运行。

这听起来既聪明又愚蠢。 还有其他意见吗?

我决定尝试另一种方法。

dynamic var product: NSNumber? = NSUserDefaults.standardUserDefaults().objectForKey("product") as? NSNumber

override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
    switch keyPath {
    case "product":
        if let value = product {
            defaults.setInteger(value.integerValue, forKey: "product")
        } else {
            defaults.removeObjectForKey("product")
        }

    default:
        break
    }
}

init() {
    self.addObserver(self, forKeyPath: "product", options: NSKeyValueObservingOptions.allZeros, context: nil)
}

deinit {
    self.removeObserver(self, forKeyPath: "product")
}

更改的结果完全相同。 代码在消息分发期间崩溃。 唯一的好处是,我认为这是比原始代码更干净的代码。

找到了!

向所有人道歉,问题不在我介绍的代码中,而是在观察者中。

问题是我的观察者之一是自定义MKAnnotaionView ,它正在观察product价值。 但是,当注释离开当前地图视图时,将从地图中删除annoationView并进行缓存。 willChangeValueForKey方法被触发时,它试图到达注释willChangeValueForKey并失败,从而触发崩溃。

required init(coder aDecoder: NSCoder) {
    Static.info.addObserver(self, forKeyPath: "product", options: NSKeyValueObservingOptions.allZeros, context: nil)
}

deinit {
    Static.info.removeObserver(self, forKeyPath: "product")
}

override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
    switch keyPath {
    case "product":
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
            self.refreshText()
            self.refreshImages()
        }

    default:
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}

override var annotation: MKAnnotation! {
    get {
        return super.annotation
    }
    set(newAnnotation) {
        super.annotation = newAnnotation
        if let annot = newAnnotation as? MyAnnotation {
            self.refreshText()
            self.refreshImages()
        }
    }
}

原始代码在init设置了观察者,并在deinitdeinit 这是一个可以正常工作的新代码片段。

required init(coder aDecoder: NSCoder) {
}

deinit {
    if annotation != nil {
        Static.info.removeObserver(self, forKeyPath: "product")
    }
}

override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
    switch keyPath {
    case "product":
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
            self.refreshText()
            self.refreshImages()
        }

    default:
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}

override var annotation: MKAnnotation! {
    get {
        return super.annotation
    }
    set(newAnnotation) {
        if annotation != nil {
            Static.info.removeObserver(self, forKeyPath: "product")
        }
        super.annotation = newAnnotation
        if let annot = newAnnotation as? MyAnnotation {
            Static.info.addObserver(self, forKeyPath: "product", options: NSKeyValueObservingOptions.allZeros, context: nil)
            self.refreshText()
            self.refreshImages()
        }
    }
}

通过在注释变量中添加和删除观察者,视图在缓存时不会更新,从而避免了错误的发生。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM