42

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.

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
Guilherme
  • 7,839
  • 9
  • 56
  • 99

2 Answers2

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.)

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 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? – Guilherme Oct 05 '17 at 17:53
  • 4
    You do need to store it, for the reason I gave in the second paragraph. – matt Oct 05 '17 at 17:55
  • 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? – Guilherme Oct 05 '17 at 18:00
  • 2
    Incorrect, for the reason I gave in the second paragraph. – matt Oct 05 '17 at 18:02
  • 14
    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. – Miles Egan Oct 16 '17 at 01:04
  • 3
    @MilesEgan In iOS 11? – matt Oct 16 '17 at 14:54
  • one thing to point out is to hold a reference to the observation, otherwise it will be deallocated at the end of the function – fespinozacast Nov 07 '17 at 11:28
  • 2
    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 too – fespinozacast Nov 07 '17 at 11:29
  • 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? – Kartick Vaddadi Mar 09 '18 at 11:39
  • 2
    @VaddadiKartick See https://developer.apple.com/library/content/releasenotes/Foundation/RN-Foundation/index.html and esp. under "Relaxed Key-Value Observing Unregistration Requirements" – matt Mar 11 '18 at 19:26
  • Thanks. Is that only on macOS, as that documentation says? – Kartick Vaddadi Mar 14 '18 at 03:00
  • @VaddadiKartick Have you considered _trying_ it? It's very easy to test this. Crashing is obvious when it happens — and when it doesn't. – matt Mar 14 '18 at 03:26
  • 4
    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. – Kartick Vaddadi Mar 14 '18 at 07:38
  • 1
    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? – mfaani Sep 22 '18 at 20:08
  • 1
    @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. – matt Sep 22 '18 at 20:55
  • Thanks. Since iOS9 I thought 'NSNotificationCenter observers' go out of memory as soon the object is deallocated. What you're saying is against [this](https://stackoverflow.com/q/39663984/5175709). 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? – mfaani Sep 22 '18 at 21:03
  • 1
    @Honey I'm not saying _anything_ about NSNotificationCenter observers. This question is about KVO observers and that's all I've talked about. – matt Sep 22 '18 at 21:38
  • @matt 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. – Manuel Jan 28 '20 at 08:41
  • 1
    @Manuel 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? – matt Jan 28 '20 at 15:41
  • @matt 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. – Manuel Jan 28 '20 at 18:28
  • 1
    @Manuel 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 https://developer.apple.com/documentation/objectivec/nsobject/1409370-automaticallynotifiesobservers 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. – matt Jan 28 '20 at 18:30
  • I overlooked that, my eyes got too used to the Swift elegance. – Manuel Jan 28 '20 at 18:34
  • 1
    @ArtemZaytsev I already gave that link at the start of the "discussion"; see [here](https://stackoverflow.com/questions/46591637/in-swift-4-how-do-i-remove-a-block-based-kvo-observer#comment-85451753). – matt Nov 25 '22 at 14:17
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.

Pooja Sangle
  • 331
  • 2
  • 10
dlemex
  • 326
  • 2
  • 6