Reputation: 101
I have a AuthService class that has a method to perform asynchronous connection to login. This class has implemented NSURLConnectionDataDelegate protocol so that when the server responses, it calls the completion handler previously set by a View Controller to update UI.
This is the definition of that completion handler
@property void (^completionHandler)(LoginResult *result);
This is when the class receives server response
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *response = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
//Do something with the response and create an instance of LoginResult class
self.completionHandler(loginResult);
}
If the completion handler block merely just calls NSLog to write to console the information of the login result which is passed as argument, then it runs perfectly with no error. But when I want to call methods of the ViewController that owns the block, something strange just happens.
I know that there is a retain cycle when you include an object in a block which owns that block. So this is the way how I code it.
__block typeof(self) bself = self;
[authService login:blablabla completionHandler:^(LoginResult *result) {
[bself didReceiveLoginResult:result];
}
I assumed this will prevent from running into a retain cycle. But I got "Thread: EXC_BAD_ACESS" error when debugging.
P.S. Following codes for example run perfectly even if that property is not declared as "copy"
[authService login:blablabla completionHandler:^(LoginResult *result) {
NSLog(@"Login %@", result.success ? @"success" : @"failed");
}
Upvotes: 2
Views: 535
Reputation: 130102
The property should be declared as copy
otherwise the block will stay on stack and can be already deallocated when you call it.
Also, there are simple ways how to prevent a retain circle. Just release the block when you have used it, e.g.
self.completionHandler(loginResult);
self.completionHandler = nil;
No clever magic with __block
is neccessary. Retain cycles are allowed when they are temporary.
Edit:
If there is no reference to self
in the block, the compiler will make it a global block and it won't ever get deallocated. See http://www.cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html
Upvotes: 4
Reputation: 9168
Blocks need to be copied if you want to use them outside of the current function, so you will need to copy it before storing it in your property:
- (void)setCompletionHandler:(void (^)(LoginResult *))handler {
_completionHandler = [handler copy];
}
Then when you assign the completion handler in your login:completionHandler:
method, it will be copied before being stored in the instance variable.
In this way, the block you pass to the function will be copied before being stored in the property, and the copy will be located on the heap, not the stack, so it'll still exist when you run it later.
Upvotes: -1