Issuance of excess emissions with objects captured by blocks; save counter jumps straight from +2 to 0!

I am confused by the accidental accident that I see, which, according to the Zombies tool, is caused by the excessive release of some dictionary values. When I look at the history of objects for one of these overridden objects in the Tools, I see that its hold counter drops from +2 to 0 at one stage. (Take a look at the screenshots at the end of the post). I don’t understand how this is possible.

I have to say that I see only this crash when profiling with tools, so I believe that this may be an Apple error, but it is probably safer to assume that this is a pilot error that the tools simply expose.

In any case, I create a CFDictionary that contains some Core Foundation objects (CFStrings and CFNumbers), and then I pass it to NSDictionary * and pass it to the Objective-C method. The following is a simplified version of my code:

// creates a CFDictionary containing some CFStrings and CFNumbers void doStuff() { CFDictionaryRef myDict = CreateMyDictionaryContainingCFTypes(); dispatch_async(myQueue, ^{ [someObject receiveDictionary:(NSDictionary*)myDict]; CFRelease(myDict); // this line causes a crash. The Zombies instrument // claims a CFString value contained in this // dictionary has already been freed. }); } // ... - (void)receiveDictionary:(NSDictionary*)dict { NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSString* str1 = [dict objectForKey:@"key1"]; NSString* str2 = [dict objectForKey:@"key2"]; NSNumber* num1 = [dict objectForKey:@"key3"]; dispatch_async(myOtherQueue, ^{ [database executeUpdate:@"INSERT INTO blah (x,y,z) VALUES (?, ?, ?)", str1, str2, num1]; }); [pool drain]; } 

I thought that str1 , str2 and num1 would be treated as Objective-C objects and therefore would be captured and automatically saved when the block in -receiveDictionary: was copied by the dispatch_async call, and freed when that block was freed. Indeed, these variables seem to be captured and stored by the block. However, examining the history of objects for an overridden CFString in Tools, I can see that its reference count increases as the block is copied. It's amazing that its number of deductions drops from +2 to 0 when the block is freed (see. Screenshot at the end of the message); I don't know how to tell from the stack trace that blocks this. By the time CFRelease is called in the dictionary in a block in doStuff() , some of its values ​​are already freed, and the program will work.

So where did the extra release come from? How can the object counter be kept on the straight side from +2 to 0, as the device shows?

On a whim, I forced the second block to save the entire dictionary, for example:

 dispatch_async(myOtherQueue, ^{ [database executeUpdate:@"INSERT INTO blah (x,y,z) VALUES (?, ?, ?)", str1, str2, num1]; [dict self]; }); 

It is like the accident will disappear; tools stop reporting zombies at least. I cannot understand for life why this works; I just have to make sure that the dictionary values ​​that interest me are stored in the block, and not the entire dictionary. So what is going on?


The fixtures list the following object history for the Zombie CFString, while the object counter is saved. I have included screenshots for interesting events.

# 0 +1 Created by CFString
# 1 +2 CFString added to dictionary
# 2 +1 CFString released
# 3 +2 CFString is saved when copying a block to -receiveDictionary:
# 4 +0 What ...? The object’s held account fell straight from +2 to 0!
# 5 -1 CFDictionary exited, causing a crash

+7
source share
3 answers

Finally, I caught an error - it turned out that this is not a problem with zombies at all, but a problem of unconnected memory in a program for decoding base64 data. It has nothing to do with save / release, blocks or GCD. Sigh.

Looking back, this should have been more obvious. The fact that my program crashed shortly after the Tools reported that the redefined object was supposed to be the key - if it were a zombie problem, you would not expect a crash. (I think?). It is believed that the number of deductions, passing from +2 to 0, offered something other than a simple reissue.

So what did I find out?

  • Do not copy-paste the code without a thorough check. All base64 conversion procedures are not created equal. (In particular, calling realloc without using its return value is false, false! It is a pity that Static Analyzer does not mark this.)
  • Do not rely solely on tools - other tools, such as Valgrind, may be helpful. In this case, Valgrind gave me more accurate information.
0
source

What do you use as CFDictionaryKeyCallBacks and CFDictionaryValueCallBacks when creating a dictionary in CreateMyDictionaryContainingCFTypes() ? I can easily replicate this problem if I go to NULL for both, but I can not replicate it if I go to &kCFTypeDictionaryKeyCallBacks and &kCFTypeDictionaryValueCallBacks .

0
source

When copying a block, it will implicitly contain some Objective-C object in its scope, and then it will also implicitly release these objects when the block is freed.

CFDictionaryRef is a free bridge type for NSDictionary , and Objective-C objects in blocks. This means that you do not need to perform additional memory management.

Let me comment on your code and mark the rating order.

 void doStuff() { // 1. myDict must have a retainCount of 1, you named your function Create // and promised so according to Core Foundation men.rules. CFDictionaryRef myDict = CreateMyDictionaryContainingCFTypes(); // 2. dispatch_async will copy your block and retain myDict, since it is in // scope of the block, myDict has a retainCount of 2 dispatch_async(myQueue, ^{ // 4. Block is execute some time later, myDict has a retainCount of 1. [someObject receiveDictionary:(NSDictionary*)myDict]; // 5. Block is done and will be released, along with scoped objects // on exit, retainCount reaches 0, and myDict is released. }); // 3. Release your own copy before function ends, retainCount of 1 CFRelease(myDict); // this line causes a crash. The Zombies instrument } 
-one
source

All Articles