使用removeObserver崩溃:forKeyPath:在Foundation中

时间:2015-05-11 10:17:33

标签: objective-c crash key-value-observing

我遇到了从" Crashes"中检索到的以下崩溃日志的一些问题。 Xcode中的部分。此崩溃报告只会影响少数设备。

我已经分析了这个问题,但我猜这是Apple框架上的一个错误。但我无法找到复制它的方法。

这里有类似的讨论:Help with crash in removeObserver:forKeyPath:

任何提示?

  

线程0名称:线程0崩溃:

     

0基金会   0x23507591 _NSKeyValueReplaceObservationInfoForObject + 69   (NSKeyValueObserving.m:1166)

     

1基金会     0x23506fe7 - [NSObject(NSKeyValueObserverRegistration)   _removeObserver:forProperty:] + 327(NSKeyValueObserving.m:1552)

     

2基金会   0x23506b03 - [NSObject(NSKeyValueObserverRegistration)removeObserver:forKeyPath:] + 163(NSKeyValueObserving.m:1696)

     

3基金会   0x235069a7 - [NSObject(NSKeyValueObserverRegistration)   removeObserver:forKeyPath:context:] + 219(NSKeyValueObserving.m:1663)

     

4 ApplicationName   0x0002e233 - [Supervisor removeObjectObserver:forKeyPath:] + 115(Supervisor.m:344)

其中removeObjectObserver:forKeyPath:

- (void) removeObjectObserver:(id)object forKeyPath:(NSString *)keyPath { 

    @try {        
        [object removeObserver:self forKeyPath:keyPath context:PrivateKVOContext];

    } @catch (NSException *exception) { }
}

3 个答案:

答案 0 :(得分:8)

Observers中的Objective-C必须特别注意:不要将相同的观察者倍数添加到同一对象的属性中,如果有一个,则将其删除:

  if ([self observationInfo]) {
        @try {
            [self removeObserver:self forKeyPath:keyPath];
        }
        @catch (NSException *exception) {}
    }

您正在遇到崩溃,因为您尝试删除两次观察者,或者您正在移除一个不存在的观察者。

您应该以这种方式添加observers

[yourObject addObserver:self forKeyPath:keypath options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial context:nil/yourContext];

编辑: 您可以删除已经解除分配的对象上的观察者,从而导致此崩溃。

  if (object && [self observationInfo]) {
    @try {
                [self removeObserver:self forKeyPath:keyPath];
            }
            @catch (NSException *exception) {}
}

答案 1 :(得分:5)

通常你有一个ivar能够知道你对象的关键路径是否在此刻观察。像@property(...)BOOL textFieldTextObserving; 并且您的添加/删除观察方法应该在添加/删除之前检查此属性,以避免添加/删除观察者两次。 如果有许多观察对象和键路径(将@(BOOL)保留为对象和 - 标识符作为键),也可以使用NSDictionary。

无论如何,使用@ try-exception做事并不是推荐的Objective-C方式。 Apple文档说:

"You should not use a try-catch block in place of standard programming checks for Objective-C methods. In the case of an NSArray, for example, you should always check the array’s count to determine the number of items before trying to access an object at a given index. The objectAtIndex: method throws an exception if you make an out-of-bounds request so that you can find the bug in your code early in the development cycle—you should avoid throwing exceptions in an app that you ship to users." https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/ErrorHandling/ErrorHandling.html

答案 2 :(得分:0)

给出答案为时已晚,但我面临着同样的问题。所以我决定为别人写这个。

  

注意:   崩溃的主要原因是您尝试在添加之前删除观察者。

我创建了一些扩展,可以帮助您安全地删除观察者。雨燕5。

现在,您可以在添加它之前将其删除,而不会崩溃。确保还删除了deinit中的观察者。

用法:

objectToObserve.safeRemoveObserver(self, keyPath: "myDate", context: &myContext)

扩展:

extension NSRegularExpression {

convenience init(_ pattern: String) {
    do {
        try self.init(pattern: pattern)
    } catch {
        preconditionFailure("Illegal regular expression: \(pattern).")
    }
}

func matches(_ string: String) -> Bool {
    let range = NSRange(location: 0, length: string.utf16.count)
    return firstMatch(in: string, options: [], range: range) != nil
   }
 }

extension NSObject {

func safeRemoveObserver(_ observer: NSObject, keyPath: String, context: inout Int) {
    let result = checkIfAlreadyAdded(keyPath: keyPath, context: &context)

    if result {
        removeObserver(observer, forKeyPath: keyPath, context: &context)
    }
}

fileprivate func address(_ o: UnsafeRawPointer) -> Int {
    return Int(bitPattern: o)
}

fileprivate func checkIfAlreadyAdded(keyPath: String, context: inout Int) -> Bool {

    guard self.observationInfo != nil else { return false }

    let info = Unmanaged<AnyObject>
           .fromOpaque(self.observationInfo!)
           .takeUnretainedValue()

    let contextStr = NSString(format: "%p", address(&context))
    let infoStr = info.description ?? ""

    let regex = NSRegularExpression("\(keyPath).*[a-z].*\(contextStr)")
    let result = regex.matches(infoStr)

    return result
  }
}