You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
NSKeyValueObservation unregisters its observation in deinit (assuming nobody calls invalidate()). This is a problem because it opens up a race condition whereby a second thread sends a KVO change notification in between the NSKeyValueObservation object decrementing its retain count to zero and it actually executing its deinit. If this race is hit, the NSKeyValueObservation.observeValue(forKeyPath:of:change:context:) method will be invoked on a dead object, which will potentially behave badly or crash.
Given the desire to unregister on deinit, I believe the only proper solution is as follows:
Use a second helper object as the actual observing object and host of the observeValue(forKeyPath:of:change:context:) method. The NSKeyValueObservation object holds onto this second object and calls invalidate() when the NSKeyValueObservation object enters deinit.
Add this second object as an associated value on the observed object (and then remove it in invalidate()). This way the second object will be guaranteed to be alive while the observed object is sending its KVO change notice. Note that it's not sufficient merely to ensure the second object is alive throughout the invalidate() call, as a KVO change notice may have been triggered on a background thread but not yet completed.
This approach still has the potential issue of someone calling objc_removeAssociatedObjects(_:) on the observed object, but in this rather unlikely event, the behavior would simply degrade to what we already have today.
The text was updated successfully, but these errors were encountered:
I've got a proof-of-concept patch written for this already, but without tests yet. If I have time I'll publish it tonight.
That said, I'm not sure how to go about testing this, since it's a race condition. Do we have precedent for tests on threading-related race conditions?
Environment
Current Swift master (14d4235b571d7a4e2ae51854f0d665f96959d182)
Additional Detail from JIRA
md5: 83e96e9c5b2276eba39cabcf7e32f97b
Issue Description:
NSKeyValueObservation
unregisters its observation indeinit
(assuming nobody callsinvalidate()
). This is a problem because it opens up a race condition whereby a second thread sends a KVO change notification in between theNSKeyValueObservation
object decrementing its retain count to zero and it actually executing itsdeinit
. If this race is hit, theNSKeyValueObservation.observeValue(forKeyPath:of:change:context:)
method will be invoked on a dead object, which will potentially behave badly or crash.Given the desire to unregister on
deinit
, I believe the only proper solution is as follows:Use a second helper object as the actual observing object and host of the
observeValue(forKeyPath:of:change:context:)
method. TheNSKeyValueObservation
object holds onto this second object and callsinvalidate()
when theNSKeyValueObservation
object entersdeinit
.Add this second object as an associated value on the observed object (and then remove it in
invalidate()
). This way the second object will be guaranteed to be alive while the observed object is sending its KVO change notice. Note that it's not sufficient merely to ensure the second object is alive throughout theinvalidate()
call, as a KVO change notice may have been triggered on a background thread but not yet completed.This approach still has the potential issue of someone calling
objc_removeAssociatedObjects(_:)
on the observed object, but in this rather unlikely event, the behavior would simply degrade to what we already have today.The text was updated successfully, but these errors were encountered: