I'm trying to understand why this code is leaking, using ARC:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
}
As you can see, I put a block inside a collection (NSMutableDictionary, but it's the same if I use NSDictionary, NSArray ecc...), then the method returns and the dictionary is deallocated. The block should then be released. But, using instruments, I see a leak
"just to be sure" that the block has no other references, I added this line at the end of the method:
[dict setObject:[NSNull null] forKey:@"Key"];
same result.
I've found this post but the answers point to another problem: Blocks inside NSMutableArray leaking (ARC)
Then, this is the magic: If I change this line:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
to:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[aBlock copy] forKey:@"Key"];
the leak disappear.
I know that, under non-ARC, before passing a reference of a block literal, I must copy it (when declared literal, it's on the stack, so I need to copy it to the heap before passing outside the scope of the function where is declared)...but using ARC I shouldn't care about it. Any indication? This is happening with all versions from 5.0 to 6.1.
EDIT: I've made some tests, trying to understand if I'm doing something wrong or if there is some bug...
First: Am I reading wrong instruments informations? I don't think, the leak is real and not my mistake. Look at this image...after executing the method 20 times:
Second: what happens if I try to do the same thing in a non arc environment? this adds some strange behavior:
same function in NON-ARC environment:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
[aString release];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:@"Key"];
}
With the previous non-arc implementation, I have a leak only for the block (not for the string) Changing the implementation to use an autorelease on the mutable string declaring solves the leak!!! I can't understand why, and I'm not sure if it could be related to the main post issue
// version without leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[[NSMutableString alloc] init] autorelease];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:@"Key"];
}
CONCLUSIONS:
After various answers and further investigating, I understood some things:
1- Apple docs says that you must use [^{} copy] when you pass a block to a collection. This is because ARC doesn't add the copy itself. If you don't, the collection (array, dictionary..) sends a retain on a STACK ALLOCATED OBJECT - which does nothing. When the method ends, the block goes out of scope and becomes invalid. You will probably receive a bad access when using it. But note: this is not my case, I'm experiencing a different problem
2- the problem I'm experiencing is different: the block is over-retained (the opposite problem --> the block is still alive even when it shoulnd't be). Why? I've found this: in my example, I'm using this code
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
this code, under NON-ARC, stores a reference (aBlock) to the literal block. The block is allocated on the stack, so if you NSLog(@"%p", aBlock) -> you will see a stack memory address
But, this is the "strange" (I don't find any indication in Apple docs), if you use the same code under ARC and NSLog aBlock address, you will see that now it's on the HEAP! For this reason the behavior is different (no bad access)
So, both incorrect but different behavior:
// this causes a leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
}
// this would cause a bad access trying to retrieve the block from the returned dictionary
- (NSMutableDictionary *)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
return [NSMutableDictionary dictionaryWithObject:^{
NSMutableString __unused *anotherString = aString;
} forKey:@"Key"];
}
3 - about my last test under NON-ARC, I think that the release is in the wrong place. I released the string before adding the block to the dictionary with copy-autorelease. The block automatically retains the variables referenced inside the block, but the retain message is sent at the copy moment, not at the declaration. So, If I release aString before copying the block, it's retain count goes to 0, then the block sends a retain message to the "zombie" object (with unexpected behavior, it can leak the block, crash, ecc ecc)