TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

NSNotificationCenter with blocks considered harmful

90 pointsby dcauntover 11 years ago

11 comments

AshFurrowover 11 years ago
There&#x27;s nothing really magical about this – don&#x27;t cause retain cycles, everyone knows __block semantics changed with ARC, keep the returned value from the block-based NSNotificationCenter method, etc. Standard stuff.<p>The only thing that surprised me was the reference cycle in NSAssert. Then again, given Apple&#x27;s poor regard for TDD, that shouldn&#x27;t surprise me.
评论 #6767799 未加载
评论 #6767796 未加载
评论 #6767943 未加载
评论 #6767804 未加载
评论 #6767650 未加载
评论 #6767911 未加载
评论 #6767785 未加载
评论 #6767757 未加载
chrisdevereuxover 11 years ago
This really applies equally to any block that captures self. Blocks are, IMHO, the one place where ARC really falls down versus garbage-collection.
评论 #6767562 未加载
pjungwirover 11 years ago
The book <i>Programming iOS 6</i> by Matt Neuburg recommends the &quot;weak-strong dance&quot; for this, like so (p. 326):<p><pre><code> __weak MyClass* wself = self; self-&gt;observer = [[NSNotificationCenter defaultCenter] addObserverForName:@&quot;heyho&quot; object:nil queue:nil usingBlock:^(NSNotification *n) { MyClass *sself = wself; if (sself) { NSLog(@&quot;%@&quot;, sself); } }]; </code></pre> Do other people agree this fixes the problem?<p>Also, it&#x27;s my understanding closures only cause retain cycles if the method taking the block <i>copies</i> the block, per the docs: &quot;When a block is <i>copied</i>, it creates strong references to object variables used within the block.&quot; Therefore this dance is not necessary for e.g. [NSURLConnection sendAsynchronousRequest:queue:completionHandler:]. Is that correct?
评论 #6770799 未加载
评论 #6770813 未加载
kybernetykover 11 years ago
A little off topic:<p>If you over-do it then NSNotifications easily can become distributed goto. Once I had to add features to a Mac application that made heavy (ab)use of NSNotifications. Following code paths was a nightmare.<p>I tend to prefer the delegate pattern because there the relationships between objects are clear. Even if it means more work (creating glue code) - your sanity is worth it.
obiterdictumover 11 years ago
On a related note, I&#x27;ve run into a similar problem in C++, but opposite effect. A lambda <i>won&#x27;t</i> keep my object alive if I try to capture a shared_ptr <i>member</i>, because C++ lambdas, similarly to blocks, by default capture implicit reference to &quot;this&quot;, rather than individual instance variables.<p><a href="http://stackoverflow.com/q/7764564/23643" rel="nofollow">http:&#x2F;&#x2F;stackoverflow.com&#x2F;q&#x2F;7764564&#x2F;23643</a>
pothiboover 11 years ago
Nice post! Are you always testing Apple&#x27;s implementation details in your TDD? To me it seems the actual notification submission shouldn&#x27;t be tested, only that it was called with the proper value?<p>Just a heads up though: The code snippet were hard to read because of the indentation. May I suggest<p><pre><code> cleanupObj = [[NSNotificationCenter defaultCenter] addObserverForName:notificationName object:nil queue:nil usingBlock:^(NSNotification *note) { &#x2F;&#x2F; Code! }]; </code></pre> It might not be your coding style but in very narrow container, it makes it easier to read ;)
Strilancover 11 years ago
There are three related mistakes contributing to this bug:<p>1. The test is depending on dealloc being done eagerly.<p>2. The Attempt class puts unsubscribe code in its dealloc method.<p>3. The Attempt class does not force callers to control its lifetime.<p>I&#x27;ll go into more depth on these.<p>1. Cleanup is often deferred. Dealloc may only run AFTER the test instead of DURING the loop (e.g. I think NSArray defers releasing its items in some cases). Tests that depend on cleanup occurring eagerly, without explicitly waiting or forcing it, are brittle. Given that Attempt uses dealloc to unsubscribe, and we are testing that it unsubscribed, we should at the very least allocate it in an autoreleasepool that ends before we test that it unsubscribed.<p>2. Putting unsubscribe code in dealloc is a great way to upgrade minor memory leak bugs into major behavior bugs. The article gives fantastic examples of this happening many different ways. Don&#x27;t rely on the object happening to go out of scope at the right time. If your object&#x27;s lifetime is controlling whether or not side effects occur, that&#x27;s a bad situation to be in.<p>3. Someone has to be responsible for when unsubscribing happens (since we took it away from dealloc), and the someone most in a position to know when is the caller. We should force them to tell us our subscription lifetime. I like to control lifetimes with cancellation tokens [1], so I&#x27;d write the attempt class like this:<p><pre><code> @implementation Attempt -(instancetype) initUntil:(TOCCancelToken*)untilCancelledToken { if (self = [super init]) { id cleanupObj = ... addObserverForName stuff from Attempt1 ... [untilCancelledToken whenCancelledDo:^{ [NSNotificationCenter.defaultCenter removeObserver:cleanupObj]; }]; } return self; } - (void)setLocalCounter:(int)localCounter { ... } - (int)localCounter { ... } @end </code></pre> Here&#x27;s what the test should look like:<p><pre><code> -(void)testExample { for(int i =0; i &lt; 5; i++) { TOCCancelTokenSource* c = [TOCCancelTokenSource new]; Attempt *a = [[Attempt alloc] initUntil:c.token]; [NSNotificationCenter.defaultCenter postNotificationName:notificationName object:nil]; [c cancel]; XCTAssertEqual(counter, i+1, @&quot;Unexpected value for counter.&quot;); XCTAssertEqual(1, attempt1.localCounter, @&quot;Unexpected value for localCounter.&quot;); } } </code></pre> Hopefully I got that right. I don&#x27;t have a mac nearby to test it out at the moment, and I&#x27;ve never used NSNotificationCenter before.<p>1: <a href="http://twistedoakstudios.com/blog/Post7391_cancellation-tokens-and-collapsing-futures-for-objective-c" rel="nofollow">http:&#x2F;&#x2F;twistedoakstudios.com&#x2F;blog&#x2F;Post7391_cancellation-toke...</a>
评论 #6769415 未加载
评论 #6771940 未加载
archagonover 11 years ago
This article reminds me to brush up on my blocks! Good to have all these error cases all in one place. :)
dmisheover 11 years ago
Nice, just the other day I used NSAssert in C function and got weird crash on self.
adamcavalliover 11 years ago
typeof(self) __weak weakSelf = self;<p>self.block = ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { &#x2F;&#x2F; ... } };
jherikoover 11 years ago
I consider the whole of Objective-C harmful.<p>It lives only in the &#x27;platform&#x27; layer of my code. Grudgingly because Apple enforce it.<p>It could be worse... they could have pulled a Google and used Java - then held back the tools they develop e.g. the Java VM because they are &#x27;dangerous&#x27; and suggest that using native code is &#x27;bad&#x27; if it is just for performance or cross-platform reasons.<p>At least the interoperability with sane programming languages in Objective-C is excellent.<p>I&#x27;d point out the vast majority of memory management issues I have is when using someone&#x27;s refcounting or gc scheme. new and delete are exactly what i want all of the time. i like to tell the machine what to do and i have developed non-trivial software without leaks &#x2F;before testing for leaks&#x2F; because once you have some practice with new and delete it becomes easy and you will never look back...<p>As a mechanism for broadcasting information throughout an app NSNotificationCenter is slower, more complicated and (apparently) more dangerous than my hand rolled code. That should be shocking and its counter to the common idea that 3rd party and especially OS libraries should be better... there are a number of cases where they measurably aren&#x27;t.<p>(The overhead of the objective C message mechanism - or even the RTTI mechanism which is a small part of that, exceeds that of adding or reading something from a well implemented threadsafe data structure (which is the first step to either of these things in most cases))
评论 #6769569 未加载
评论 #6770646 未加载