[英]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。
有两种选择:
您可以关闭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` }
您可以将自动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) // }
编辑虽然此解决方案最初有效,但后来的更改使其无效。
我只是找到了解决方案,对我来说,这似乎很愚蠢。 我要做的就是像这样移动willChangeValueForKey
和didChangeValueForKey
方法调用。
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
设置了观察者,并在deinit
其deinit
。 这是一个可以正常工作的新代码片段。
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.