Has anything changed with regard to the .initial observing option in KVO?

Apparently there's now stuff in Cocoa designed to protect against simultaneous pointer access, even in the same thread. Unfortunately, this has been causing problems for me with registering for KVO using the .initial observing option (which sends an observer notification as part of the registration process to make it easier to get things set up). Because the notification happens in the same frame as the setup, the context pointer is used twice in the same frame (and therefore "simultaneously"), which never used to be a problem until now. Yes, it's definitely a potential problem, as with any form of simultaneous raw pointer access, but there's no other way to use KVO contexts!


So first of all, is this intended. behavior, and second, is there a way to fix this without making major changes to my code like abandoning .initial altogether?


Here's an example:

init() {
    ...
    //Because of the .initial option, the registration method will call observeValue before it returns.
    object.addObserver(self, forKeyPath: keyPath, options: [.initial], context: &myContextPointer)
}

func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
    if context == &myContextPointer {  //Crash happens here because we're accessing the pointer twice in the same frame
          ...
    } else {
          super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    } 
}

I had a similar problem, and it only started occuring with iOS 11 beta 8.


The problem, in my case, was that the variable I was using for the KVO context was an instance var. It has to be a global variable:


private var myContextPointer = 0


or static class variable:


class MyClass {
   private static var myContextPointer = 0

    init() {
        ...
        object.addObserver(self, forKeyPath: keyPath, options: [.initial], context: &MyClass.myContextPointer)
    }

    func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
        if context == &MyClass.myContextPointer {
              ...
        } else {
              super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    } 
}


This is explained here https://developer.apple.com/swift/blog/?id=6 (emphasis mine):


These conversions cannot safely be used if the callee saves the pointer value for use after it returns. The pointer that results from these conversions is only guaranteed to be valid for the duration of a call. Even if you pass the same variable, array, or string as multiple pointer arguments, you could receive a different pointer each time. An exception to this is global or static stored variables. You can safely use the address of a global variable as a persistent unique pointer value, e.g.: as a KVO context parameter.


The conversions they are talking about are the implicit bridging of &variableName to an UnsafeMutableRawPointer.

Has anything changed with regard to the .initial observing option in KVO?
 
 
Q