Crash with removeObserver:forKeyPath: in Foundation Crash with removeObserver:forKeyPath: in Foundation objective-c objective-c

Crash with removeObserver:forKeyPath: in Foundation


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


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


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