Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective C memory management with blocks, ARC and non-ARC

I have been using blocks for some time now, but I feel that there are things I miss about memory management in both ARC and non-ARC environments. I feel that deeper understanding will make me void many memory leaks.

AFNetworking is my main use of Blocks in a particular application. Most of the time, inside a completion handler of an operation, I do something like "[self.myArray addObject]".

In both ARC and non-ARC enabled environments, "self" will be retained according to this article from Apple.

That means that whenever a completion block of an AFNetworking network operation is called, self is retained inside that block, and released when that block goes out of scope. I believe that this applies to both ARC and non-ARC. I have ran both the Leaks tool and the Static Analyzer so that I may find any memory leaks. None showed any.

However, it wasn't until recently that I stumbled upon a warning which I couldn't figure out. I am using ARC in this particular example.

I have two instance variables that indicate the completion and failure of a network operation

@property (nonatomic, readwrite, copy) SFCompletionBlock completionBlock;
@property (nonatomic, readwrite, copy) SFFailureBlock failureBlock;
@synthesize failureBlock = _failureBlock;
@synthesize operation = _operation;

Somewhere in the code, I do this:

[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id
                                                    responseObject) {
NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
            _failureBlock(error);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"nothing");
        }];

Xcode complains about the line that calls the failureBlock, with the message "Capturing "self" strongly in this block is likely to result in a retain cycle. I believe Xcode is right: the failure block retains self, and self holds its own copy of the block, so none of the two will be deallocated.

However, I have the following questions/observations.

1) If i change _failureBlock(error) to "self.failureBlock(error)" (without quotes) the compiler stops complaining. Why is that? Is this a memory leak the compiler misses?

2) In general, what is the best practice to work with blocks in both ARC and non-ARC enabled environments when using blocks that are instance variables? Seems that in the case of completion and failure blocks in AFNetworking, those two blocks are not instance variables so they probably don't fall into the category of retain cycles that I described above. But when using progress blocks into AFNetworking, what can be done to avoid retain cycles like the one above?

I would love to hear other people's thoughts on ARC and non-ARC with blocks and issues/solutions with memory management. I find these situations error-prone and I feel some discussion on this is necessary to clear things up.

I don't know if it matters, but I use Xcode 4.4 with the latest LLVM.

like image 422
csotiriou Avatar asked Jul 29 '12 16:07

csotiriou


People also ask

Does Objective-C support Arc?

Automatic Reference Counting (ARC) is a memory management option for Objective-C provided by the Clang compiler. When compiling Objective-C code with ARC enabled, the compiler will effectively retain, release, or autorelease where appropriate to ensure the object's lifetime extends through, at least, its last use.

Does Objective-C have memory management?

Objective-C provides two methods of application memory management. In the method described in this guide, referred to as “manual retain-release” or MRR, you explicitly manage memory by keeping track of objects you own.

What is the differences between MRC and ARC?

ARC is abbreviation of Automatic Reference Counting, and MRC is abbreviation of Manual Refetence Counting.

What is ARC in Xcode?

Automatic Reference Counting (ARC) is a memory management feature of the Clang compiler providing automatic reference counting for the Objective-C and Swift programming languages.


2 Answers

1) If i change _failureBlock(error) to "self.failureBlock(error)" (without quotes) the compiler stops complaining. Why is that? Is this a memory leak the compiler misses?

The retain cycle exists in both cases. If you're targeting iOS 5+, you can pass in a weak reference to self:

__weak MyClass *weakSelf;
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
    if (weakSelf.failureBlock) weakSelf.failureBlock(error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"nothing");
}];

Now self won't be retained, and if it's deallocated before the callback is invoked, the callback is a no-op. However, it's possible that it could be undergoing deallocation while the callback is invoked on a background thread, so this pattern can create occasional crashes.

2) In general, what is the best practice to work with blocks in both ARC and non-ARC enabled environments when using blocks that are instance variables? Seems that in the case of completion and failure blocks in AFNetworking, those two blocks are not instance variables so they probably don't fall into the category of retain cycles that I described above. But when using progress blocks into AFNetworking, what can be done to avoid retain cycles like the one above?

Most of the time, I think it's better not to store blocks in instance variables. If you instead return the block from a method in your class, you'll still have a retain cycle, but it only exists from the time the method is invoked to the time the block is released. So it will prevent your instance from being deallocated during the block execution, but the retain cycle ends when the block is released:

-(SFCompletionBlock)completionBlock {
    return ^(AFHTTPRequestOperation *operation , id responseObject ) {
        [self doSomethingWithOperation:operation];
    };
}

[self.operation setCompletionBlockWithSuccess:[self completionBlock]
                                      failure:[self failureBlock]
];
like image 148
Christopher Pickslay Avatar answered Nov 15 '22 08:11

Christopher Pickslay


That means that whenever a completion block of an AFNetworking network operation is called, self is retained inside that block, and released when that block goes out of scope.

No, self is retained by the block when the block is created. And it is released when the block is deallocated.

I believe Xcode is right: the failure block retains self, and self holds its own copy of the block, so none of the two will be deallocated.

The block in question that retains self is the completion block passed to setCompletionBlockWithSuccess. self does not hold a reference to this block. Rather, self.operation (presumably some kind of NSOperation) retains the blocks while it is executing. So there is temporarily a cycle. However, when the operation is done executing, the cycle will be broken.

1) If i change _failureBlock(error) to "self.failureBlock(error)" (without quotes) the compiler stops complaining. Why is that? Is this a memory leak the compiler misses?

There should be no difference. self is captured in both cases. The compiler is not guaranteed to catch all cases of retain cycles.

like image 38
newacct Avatar answered Nov 15 '22 07:11

newacct