Crash with removeObserver:forKeyPath: in Foundation

三世轮回 提交于 2020-01-23 04:19:13

问题


I having some problems with the following crash logs retrieved from the "Crashes" section in Xcode. Only few devices are affected by this crash report.

I have analyzed the problem but I guess it's a bug on Apple framework. But I cannot find a way to replicate it.

Here a similar discussion: Help with crash in removeObserver:forKeyPath:.

Any hints?

Thread 0 name: Thread 0 Crashed:

0 Foundation
0x23507591 _NSKeyValueReplaceObservationInfoForObject + 69 (NSKeyValueObserving.m:1166)

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

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

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

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

where removeObjectObserver:forKeyPath: is

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

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

    } @catch (NSException *exception) { }
}

回答1:


Observers in Objective-C must be used with extra attention: don't add the same observer multiples time to the same object's property, and wrap the removal if there is one :

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

You are experiencing crashes because you try to remove twice the observer, or you are removing a non-existant observer.

You should add observers this way :

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

EDIT: You may remove an observer on an already deallocate object, resulting in this crash.

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



回答2:


Normally you have an ivar to be able to know whether you object's keypath observing at the moment or not. Like @property(...) BOOL textFieldTextObserving; And your add/remove-observing methods should check this property before adding/removing to avoid of adding/removing observer twice. You also can use NSDictionary if there are many observing objects and keypaths (to keep @(BOOL) as objects and -identifiers as keys).

Anyway, doing things using @try-exception is not a recommended Objective-C way. Apple docs says:

"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




回答3:


It's too late to give an answer, but I'm faced with the same problem. So i decided to write this for other people.

Note: The main reason of crash is that you try to remove an observer before add.

I have created some extensions that will help you safely remove the observer. Swift 5.

You can now remove it before adding it, without crashing. Be sure you also delete an observer in deinit.

USAGE:

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

EXTENSIONS:

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


来源:https://stackoverflow.com/questions/30165085/crash-with-removeobserverforkeypath-in-foundation

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