In Swift 4, how do I remove a block-based KVO observer?
Asked Answered
P

2

43

If I store an observer like this:

let observer: NSKeyValueObservation = foo.observe(\.value, options: [.new]) { (foo, change) in
    print(change.newValue)
}

How do I remove/disable/cleanup observer once I no longer need it?

My foo instance does not have any remove-like method that receives an NSKeyValueObservation instance, the observer itself doesn't have any remove-like either.

Paratrooper answered 5/10, 2017 at 17:33 Comment(1)
You can call invalidate on the observer to unregister itQuartering
T
47

In iOS 11, you don't have to. Just let the observer go out of scope. There is no penalty any longer for letting an observer die before the observed or for letting the observed die before the observer, so you have no actual work to do.

On the other hand, if you really want to unregister the observer, remove it from whatever is retaining it, or tell it to invalidate. (Something must be retaining it, because if you don't persist the observer, it will die and your observer function will never be called.)

(You say "if I store an observer like this", but the way you are storing it, with let, is a somewhat silly way to store the observer. It would be better to put it in a Set from which you can remove it later, or at least store it in a Optional var that you can later set to nil.)

Tedmund answered 5/10, 2017 at 17:50 Comment(23)
Then I suppose I didn't really need to store it in a variable, even though Xcode will raise a Result of call to 'observe(_:options:changeHandler:)' is unused warning. Is that correct?Paratrooper
You do need to store it, for the reason I gave in the second paragraph.Tedmund
Rephrasing my question: if I don't need to manually invalidate it, and let it die "naturally", I won't need to store it in a variable. Correct?Paratrooper
Incorrect, for the reason I gave in the second paragraph.Tedmund
I just had a crash in one of my apps caused by a dangling observation after releasing an AVPlayerItem it was observing. So in some cases you really do need to call invalidate() yourself.Randalrandall
@MilesEgan In iOS 11?Tedmund
one thing to point out is to hold a reference to the observation, otherwise it will be deallocated at the end of the functionFink
also, it's an iOS 11 only feature or Xcode 9 / Swift 3.2-Swift 4.0 thing? as a project i am working on supports iOS 10 tooFink
Your statement that we don't need to — is it only for the new block-based KVO, or also for old-style KVO on iOS 11?Cerargyrite
@VaddadiKartick See developer.apple.com/library/content/releasenotes/Foundation/… and esp. under "Relaxed Key-Value Observing Unregistration Requirements"Tedmund
Thanks. Is that only on macOS, as that documentation says?Cerargyrite
@VaddadiKartick Have you considered trying it? It's very easy to test this. Crashing is obvious when it happens — and when it doesn't.Tedmund
Crashes may also be intermittent, or happen in some code paths, which we didn't think of testing. I wouldn't do things the documentation says not to do. In this case, I'd unregister all observers. Thanks for your help.Cerargyrite
Just to be crystal clear 1. It would be better to put it in a Set as in a collection? Because you may have multiple items to observe? Interesting way. 2. I read your comments and 2nd paragraph. You can still persist it by an normal property and then once the object is deallocated then your observation is undone without doing anything. Right?Zonnya
@Honey can you think of a better way than a Set? You wouldn't want to declare multiple named instance properties just to hold each of the observers! — Yes, nothing more to do in either case; the view controller goes out of existence, the observers go out of existence, they unregister themselves as they go, and all's well that ends well. — Note that that's only for KVO observers. NSNotificationCenter observers work quite differently.Tedmund
Thanks. Since iOS9 I thought 'NSNotificationCenter observers' go out of memory as soon the object is deallocated. What you're saying is against this. I don't want its answer given here. Just that do you have a previous answer written for what you just said? Can you share its link? or I should open a new question?Zonnya
@Honey I'm not saying anything about NSNotificationCenter observers. This question is about KVO observers and that's all I've talked about.Tedmund
@Tedmund You mentioned "Relaxed Key-Value Observing Unregistration Requirements" -- am I reading the docs correctly, that the object has to have a method automaticallyNotifiesObserversForKey to indicate that it supports auto-removing of the KVO? In my case it's AVPlayerItem which does not seem to have such a method with deployment target iOS11.1.Fielder
@Fielder I don't know what you mean by that. AVPlayerItem returns true from automaticallyNotifiesObserversForKey for every key I tried, including in iOS 11. Can you show your code?Tedmund
@Tedmund you are right, I should have been more precise; the instantiated object did not have the method, but the class does have the static method automaticallyNotifiesObserversForKey, thanks for checking.Fielder
@Fielder Yes, the "Relaxed ..." doc is clear about that, this is a class method. Of course to see that, you have to be able to read Objective-C notation. :) Every class has this method; it is inherited from the base class, NSObject. See developer.apple.com/documentation/objectivec/nsobject/… The only question of interest is whether the class returns true or false for a given key. But a return value of false from a built-in KVO-compliant class is so unlikely that the probability approaches zero; no need to check, really.Tedmund
I overlooked that, my eyes got too used to the Swift elegance.Fielder
@ArtemZaytsev I already gave that link at the start of the "discussion"; see here.Tedmund
P
4

With Swift 5, I began using .observe(\.propertyName, ...) on core data objects as the tokens automatically unregister at deinit or an invalidate() call on the token.

This works remarkably well until I recently noticed that I was leaking objects. I was seeing leaked NSKeyValueObservance, NSKeyValueObservationInfo, and NSArray objects. After verifying that I was managing the tokens properly, I finally tracked down the problem.

If you perform an .observe() on a Core Data object, you must keep the object as well as the token. If the object turns into a fault before you invalidate/release the token, you will leak memory. You do not crash but once it turns into a fault you will leak memory even if you free the token.

Pun answered 1/2, 2021 at 1:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.