mikeash.com pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html commentshttp://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsmikeash.com Recent CommentsThu, 28 Mar 2024 15:40:49 GMTPyRSS2Gen-1.0.0http://blogs.law.harvard.edu/tech/rssDavid Potter - 2013-03-09 02:44:17http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsThis is great stuff. I really appreciate the articles and the comments. One problem that doesn't appear to be addressed, however, is the maintenance of KVO keys that are observed. Here are a couple scenarios: <br /> <br />1. Specifying the wrong value for the key and therefore you never get called, or you don't recognize that you got called. <br /> <br />2. Renaming keys and missing a reference. <br /> <br />These sorts of problems typically are only found at some later point when you're working on something else and one of your users 1-stars your app because you were a bonehead and made one of these errors. Ideally, these would be discovered at compile time. <br /> <br />You might say that KVO was not intended for this, that it was intended only as a loosely-coupled notification mechanism. True, but can we get the best of both worlds? <br /> <br />What do you think? <br /> <br />Thanks, <br /><b><i>David</i></b>4d71c1004fd442ae567eed0289ead5bdSat, 09 Mar 2013 02:44:17 GMTGwynne Raskind - 2012-07-19 03:10:48http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#comments<b>jpmhouston</b>: Here is the pattern I typically use for a once-off KVO: <br /> <br /><code> <br />id&lt;MAKVOObservation&gt; __block observation = [thing addObservationKeyPath:@"property" ... block: ^ (MAKVONotification *notification) { <br />&nbsp;&nbsp;&nbsp;&nbsp;[observation remove]; <br />}]; <br /></code> <br /> <br />Marking the observation <code>__block</code> insulates the value from being captured too early, and should leave ARC doing the right thing as well. <br /> <br />A retain cycle <i>does</i> result in the current version of the code. The cycle can be solved by adding "<code>userInfo = nil;</code>" to the end of the <code>-deregister</code> method in <code>MAKVONotificationCenter.m</code>, which will nil out the block when the observation is unregistered, killing its retain and allowing the observation object to be deallocated as well. <br /> <br />(I would suggest submitting a pull request with this change, as this is probably a common pattern.) <br /> <br />This still results in a memory leak in the case where the observation never fires, but the leak is nullified when the target is deallocated and the observation is automatically deregistered.437559a574aa3005d5b028747b526540Thu, 19 Jul 2012 03:10:48 GMTjpmhouston - 2012-07-19 00:02:01http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsGreat work. Q: What's the best way to remove an observation the first time it triggers? I've found need to do that a few times, the most interesting of which is for this idiom (please advise if there's anything fundamentally stupid about it):<code> <br />&nbsp;&nbsp;&nbsp;&nbsp;- doAThingWith:(id)x <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (decision) <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[x doSomething] <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[otherThing addObservationKeyPath:@"property" ..block:^(..) { <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// i want to delete this very observation here <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// and within this next recurive call, perhaps re-observe <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[self doAThingWith:x] <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}]</code> <br />Target and keypath are not likely to be unique, and if addObserver was used instead of addObservationKeyPath then even the obvious observer might not be unique either. In other words, I'd really like to use be able to do [observation remove] instead of [target removeObservation:..] or [observer stopObserving:..] <br /> <br />Are retain cycles the only thing to watch out for when doing something like:<code> <br />&nbsp;&nbsp;&nbsp;&nbsp;observation = [otherThing addObservationKeyPath ... { <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[observation remove]; <br />&nbsp;&nbsp;&nbsp;&nbsp;}];</code> <br />I've also played with adding an observation property to MAKVONotification and also adding an ObserveOnce option, does anyone else see benefits or dangers to doing either?460423bbb659572ad010232d6476664bThu, 19 Jul 2012 00:02:01 GMTGwynne Raskind - 2012-07-07 16:31:57http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#comments<b>Ole:</b> Correct; you have to manage the retain cycles in your blocks yourself, as with any other API's block-based callbacks. <br /> <br />However, one less obvious way around the problem is to make use of the <code>notification</code> object's <code>observer</code> and <code>target</code> properties, which will not cause a cycle because the block doesn't need to capture them from the enclosing scope. The down side to that, of course, is that you lose the static typing of your objects unless you do something like <code>__typeof__(self) observer = notification.observer;</code> at the top of the block (which doesn't cause a retain cycle because <code>typeof</code> is purely a compile-time expression).579d895304a7188d4eb5f161c1ad9552Sat, 07 Jul 2012 16:31:57 GMTOle - 2012-07-07 15:41:07http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsThanks for this great piece of code to Gwynne, Mike and Tony. <br /> <br />I have a question: is my understanding correct that, while MAKVONotificationCenter avoids retain cycles that are related to unregistering KVO, it does not (and cannot) deal with retain cycles that occur because I used the observed object or the observer inside the notification block? <br /> <br />So code like the following will lead to a retain cycle unless I use <code>__weak id weakSelf = self;</code>? <br /> <br /><code>[self addObservationKeyPath:@"someProperty" options:0 block:^(MAKVONotification *notification) { <br />&nbsp;&nbsp;&nbsp;&nbsp;[self doSomething]; <br />}]; <br /></code>9d1c74918104a9892b19603ab9ea01eeSat, 07 Jul 2012 15:41:07 GMTJody - 2012-05-26 07:56:52http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsLike most other comment authors, I also have my own rolled version of KVO to handle auto-removal and blocks - you'd think with the focus on ARC and blocks that it would have been addressed. I have gone to great lengths to provide optional "on-dealloc" usage options which provide using associated objects and/or swizzling dealloc. I have provided options, mainly because of my question about iOS (see a few paragraphs later). <br /> <br />I will say that I did not know about imp_implementationWithBlock before reading this post. Man, I really like that. <br /> <br />Using associated objects is the most "orthodox" method in that it has been specifically blessed by Apple. However, the associated objects are removed after all inherited dealloc calls. The memory has yet to be released, but just about everything else in the dealloc chain has happened. <br /> <br />This is OK in some aspects, but for unregistering KVO it comes too late in several regards (for both observed and observer). <br /> <br />However, my main issue with swizzling dealloc is for iOS, and the reported denial of applications that swizzle dealloc. Since I've yet to see an authoritative stance in writing (or video), I'm a bit undecided as to how I should approach swizzling dealloc. <br /> <br />Can anyone help shed some light as to Apple's stance on apps that swizzle dealloc? All I can find are posts about some apps being denied, but nothing authoritative. <br />95181dabc81fb0f0542e2b726dd243a5Sat, 26 May 2012 07:56:52 GMTJoachim Bengtsson (nevyn) - 2012-03-21 17:48:43http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsThanks for the thoughts on swizzling dealloc, Gwynne. I've been thinking about it a lot, in particular in relation to autorelease problems we are having in our code base (depending too much on the associated object, and the observation being invalidated just slightly too late), and I've concluded that you're completely right. Swizzling still makes me scared, but I'll try it out next time I'm doing infrastructure work. <br /> <br />Mark Aufflic: Indeed, the "Notification Center" is completely superflous, I have no idea why I wrote it like that. As you can see in my code, I don't use any ivar storage at all, and on top of that have an NSObject category that removes the Notification Center concept. <br /> <br />Your implementation looks very simple and straightforward :) Thanks for the code, might reuse some of it in mine, if that's alright...6b772ee851b7a7fe2686658cb2c3146cWed, 21 Mar 2012 17:48:43 GMTMark Aufflick - 2012-03-13 02:28:03http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsWhile a big user of some MA* classes, I've never quite been happy to use MAKVONotificationCenter for a few reasons, including the fact that a central notification center doesn't seem like the right model. Unlike NSNotifications KVO is never distributed nor multicast - it's purely point to point. <br /> <br />The comment from Joachim got me thinking about modelling the observation as an object, but Gwynne pointed out some issues with SPKVONotificationCenter (and there's that notification center concept again). <br /> <br />Enter PMPKVObservation! Totally standalone .m/.h pair that you can include in your project. As the observer you are responsible for managing the lifecycle of the observation, except where it comes to the possibly early release of the observed object (in which case a swizzle+associated object takes care of it for you). <br /> <br />It's about as minimal as I think it can be and still be effective. I haven't used it in too much anger yet so I'd love feedback. <br /> <br /><a href="https://github.com/aufflick/PMPKVObservation">https://github.com/aufflick/PMPKVObservation</a>455a572fc9f5f0520fa4952a897e12daTue, 13 Mar 2012 02:28:03 GMTAlex - 2012-03-05 21:15:33http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#comments@Gwynne: you're right, shadowing a local variable (albeit an invisible one) that triggers a warning may indeed prove inconvenient. But you still have a choice of naming that parameter something like 'self_' and using 'self_-&gt;ivar' inside the block. <br /> <br />One could argue that the '__block' approach is prettier, but I'd say that passing the observer as an argument to the block grants you some level of safety as it eliminates the possibility to accidentally change the value of 'self' inside the block.218a6d8400c95e3c736e354457593b88Mon, 05 Mar 2012 21:15:33 GMTGwynne Raskind - 2012-03-05 18:31:42http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#comments<b>Alex:</b> Shadowing <code>self</code> is a neat trick, but unfortunately a non-starter for me; I always build with both <code>-Werror</code> and <code>-Wshadow</code>. I've found a lot of bugs that way. <br /> <br />I also store the observer helpers as associated objects, while returning the opaque reference; that way the caller can decide which method works better for them. <br /> <br />I'd love to see Apple take a hint from all these KVO wrappers, yeah :). Unfortunately, experience has shown that the likeliest way for that to happen is for all of us to file Radars and wait three OS versions :(.330aed249597241e80c44844f918d408Mon, 05 Mar 2012 18:31:42 GMTAlex - 2012-03-05 16:30:02http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsThanks for the awesome post, Gwynne! And what an interesting discussion in the comments! <br /> <br />I like your point that when using blocks an observer object suddenly has not that much use at all. However, there's a minor usability issue that it arises with it: an observer must be declared with the __block modifier or else it will be retained. I liked Jonathan Wight's approach to this issue: he uses a block, the first argument of which is called 'self'. When you want to add self as an observer, this argument shadows the actual self and so this actual self is not retained by the block, but can still be accessed via the shadowing 'self' argument. <br /> <br />I have used the trick in my own take on simplifying KVO with blocks. Here's the code -- <a href="https://github.com/alco/TastyKVO">https://github.com/alco/TastyKVO</a>. I used dealloc swizzling to automatically remove observers too. And in the case of adding an observer, instead of returning an object that can be used later to unsubscribe from notifications, I store all the needed references in associated objects to make it possible to call 'removeAllObservers' on the target (the object being observer) or 'stopObservingAllTargets' on the observer, respectively. <br /> <br />All in all, I think we all agree on one thing: KVO is an extremely useful technology at its heart, but it was unlucky to get a universally despised API that forces developers to come up with their own wrappers. Is there a chance we'll get an official revamp from Apple some day?2a38845f5972844ac1eabf63f648be5fMon, 05 Mar 2012 16:30:02 GMTGwynne Raskind - 2012-03-03 17:35:29http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#comments<b>nevyn</b>: <code>MAKVONotificationCenter</code> has the option of explicit lifetime assignment (both by returning an "observation" object and by having the option not to do the swizzling). I agree that asynchronicity can be difficult; my insistence on doing the removal implicitly comes from experience, in that I've only quite rarely had cause to remove an observation anywhere other than dealloc - and most of the times when I have had to do with retain cycles. <br /> <br />(Note, I distinguish "having cause" from "having the possibility"; there are several times when I could have explicitly removed an observation earlier, but where it did no harm either performance or logic wise to leave it until dealloc, and it almost always simplified code to do so.) <br /> <br />I would have liked to avoid swizzling, of course, as I would expect any experienced Objective-C coder to do, but the fact (as with so many of Apple's frameworks) was that Apple had simply made it impossible, whether purposefully or not. KVO is too paranoid about leaving observers registered at dealloc time, perhaps correctly (though the phrase "observation info was leaked, and may even become mistakenly attached to another object" frightens me, making me wonder what sort of hackery is afoot to make that possible - I saw it happen several times while writing the unit tests).72b7eefbbd2cf686edbe8d161464fb66Sat, 03 Mar 2012 17:35:29 GMTnevyn - 2012-03-03 17:09:13http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsHmm. That's a good point. The trick is not needed when you are subscribing to objects that you *know* will live longer than the object that is subscribing; but when that's not the case (e g if you are subscribing to a property of an object you own in an invar), it is. <br /> <br />In SPDepends, in these cases, we make sure to call SPRemoveAssociatedDependencies(self) as the first thing in dealloc. It's easy to forget, and something I've wanted to make less error prone. Swizzling dealloc is perhaps the best approach here :/ <br /> <br />I'm not absolutely convinced though: asynchronicity is difficult, and you should always be very aware of where you start and where you should stop receiving async events. I might remove the associated objects feature from SPDepends and have its users always assign the dependency to an instance variable, so that its existence is explicit and clearly visible in code. <br /> <br />Sorry for the harshness of my first comment, I'm not sure why I wrote it like that...e03799dd03d74c9af4994b49ead8ded1Sat, 03 Mar 2012 17:09:13 GMTGwynne Raskind - 2012-03-03 01:36:03http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsAs crazy as it may seem, associated objects are <i>not</i> a solution here. <br /> <br />Using them was my first idea. <br /> <br />Unfortunately, as it turned out, associated objects are released too late in the object deallocation process. By that time, KVO has already marked any remaining observations as problems and spit out its warnings to console. It's not clear whether or not unregistering the observation in an associated object even works after that point, and it certainly wasn't considered acceptable to have to tell users "don't worry about the OS spewing warnings that you're risking crashes, it's lying", especially since there are circumstances in which those warnings would still be legitimate. Not to mention that apps which spam the console with spurious messages are generally considered to be poorly designed (I'm looking at you, Adium).beda1cef6fce362a0bd5265df76a4310Sat, 03 Mar 2012 01:36:03 GMTSteve Weller - 2012-03-02 23:40:49http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsYou may be able to avoid swizzling dealloc by attaching an associated object and then just doing your wicked deeds in *its* dealloc.c1034808cdf329c7b4262314e8ace7a0Fri, 02 Mar 2012 23:40:49 GMTJoachim Bengtsson - 2012-03-02 23:12:53http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsSwizzling dealloc to get save unregistrations is rather crazy to me. There IS already a native concept in Objective-C to manage an auxiliary resource, but I'll get to that. <br /> <br />My biggest quibble by far with the KVO interface is that it exposes a concept that requires resource management, but does not represent it as an object! This results in basically all of KVO's problems. If a KVO observation was an object, unregistering twice would be impossible. There would be no strange context argument. You wouldn't be able to affect the registrations of superclasses. You seem to have reached basically the same conclusion, but not as the core of the solution. <br /> <br />In <a href="http://overooped.com/post/7456709174/low-verbosity-kvo">http://overooped.com/post/7456709174/low-verbosity-kvo</a> , I outline my own SPKVONotificationCenter. It returns KVOObservation objects: if that object is released, so is the KVO subscription, since the two are the same. <br /> <br />Thus, the way to make sure that a KVO observation go away when your subscribing object goes away, is to save your observation in a retained instance variable, and managed like any other ObjC object. No need to swizzle dealloc, no need for any magic, you just code ObjC like you always have. <br /> <br /> <br />If you are lazy and don't want to write lots of code such as an extra ivar and dealloc line (and you are, and you should be!), I do introduce one slightly magic concept in my macro $depends (or SPAddDependency, if you are allergic to macros). However, I don't swizzle dealloc, but instead I use ObjC's built-in concept for auxiliary resources: associated objects. <br /> <br />I also don't see the need of having a magic MAKVOKeyPathSet. Just build an abstraction on top of your KVO notification center. $depends does this too. <br /> <br />The end result is that you can type a single line of code that creates multiple registrations to multiple dependent objects, sets it up to be coupled to the lifetime of the calling object, and triggers a single Prior callback to a given block, and callbacks whenever any of the dependencies update: <br /> <br /><code> <br />$depends(@"draw center of gravity", <br />&nbsp;&nbsp;physicsEngine, @"entities.position", @"entities.velocity" <br />&nbsp;&nbsp;graphicsEngine, @"shouldDrawCoG", @"debuggingColor", <br />&nbsp;&nbsp;^{ <br />&nbsp;&nbsp;&nbsp;&nbsp;if(!graphicsEngine.shouldDrawCoG) <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return [graphicsEngine clear]; <br />&nbsp;&nbsp;&nbsp;&nbsp;[graphicsEngine setForegroundColor:graphicsEngine.debuggingColor]; <br />&nbsp;&nbsp;&nbsp;&nbsp;[graphicsEngine drawLineFrom:[physicsEngine.entities valueForKeyPath:@"@average.position"] <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pointing:[physicsEngine.entities valueForKeyPath:@"@average.position"]]; <br />}); <br /></code> <br /> <br />Bindings are great a lot of the time. When they aren't, $depends is.714a2f2aafbe38bc99ef8ab3f2403b0dFri, 02 Mar 2012 23:12:53 GMTmikeash - 2012-03-02 23:11:29http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsThis swizzling implementation should work fine on classes that don't implement dealloc. Did we miss something?752ff9d299f91e62eb1f327160770381Fri, 02 Mar 2012 23:11:29 GMTJonathon Mah - 2012-03-02 21:41:03http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsOh man, swizzling dealloc seems bad. Especially as many classes now don't even implement dealloc (so you'd have to add it with a call to super, instead of swizzling). I have discovered an intriguing solution to this, which this margin is too narrow to contain.3fbb984292f0b3c460e564ffc865a8bfFri, 02 Mar 2012 21:41:03 GMTJohan Kool - 2012-03-02 16:27:36http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsIt isn't directly obvious from this article where to grab the code. For others looking for it, you can find it here: <br /> <br /><a href="https://github.com/mikeash/MAKVONotificationCenter">https://github.com/mikeash/MAKVONotificationCenter</a>bdbd4d5484f4eff61b732386a10c89d4Fri, 02 Mar 2012 16:27:36 GMTErik Tjernlund - 2012-03-02 16:22:53http://www.mikeash.com/?page=pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html#commentsAfter all that, how about some code showing examples actual usage? Give me some ice cream for dessert after all that broccoli.fcdce41656edc607d96c04cdf5517618Fri, 02 Mar 2012 16:22:53 GMT