Reputation: 2815
I have some recursive block code in objective-c that is causing a EXC_BAD_ACCESS error.
- (void) doSomethingWithCompletion:(void (^)())completion {
if (completion) {
dispatch_async(dispatch_get_main_queue(), completion);
}
}
- (void) testBlocks {
NSString *testString = @"hello";
__block NSInteger count = 0;
__block __weak void (^weak_block)(NSString *);
void(^strong_block)(NSString *);
weak_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(@"number: %zd", count);
if (++count < 10) {
weak_block(str);
}
}];
};
strong_block(testString);
}
The error happens on weak_block(str) which i assume is because it is released when dispatch_async is called. Calling strong_block(str) in it's place when it's declared with __block like so:
__block void(^strong_block)(NSString *);
Causes a warning 'Capturing 'strong_block' strongly in this block is likely to lead to a retain cycle'.
So I changed the testBlock method to not use a weak reference like so:
- (void) testBlocks {
NSString *testString = @"hello";
__block NSInteger count = 0;
__block void (^inner_block)(NSString *);
void(^strong_block)(NSString *);
inner_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(@"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
strong_block(testString);
}
But I am unsure if this causes a retain cycle or if adding
__block void (^inner_block)(NSString *) = weak_block;
inside the block instead would cause a retain cycle as well. What is the correct way to handle this situation?
Upvotes: 5
Views: 726
Reputation: 122449
It crashes because the block (pointed to by weak_block
, strong_block
) has already been deallocated by the time the "completion" block runs, and calling a block with a nil
block pointer crashes.
The block is deallocated because there are no strong references to it after testBlocks
returns.
The second one will have a retain cycle because the block captures inner_block
, which holds a strong reference to itself.
The proper way is to make a strong reference from the captured weak reference inside the block, and let the completion block capture that:
- (void) testBlocks {
NSString *testString = @"hello";
__block NSInteger count = 0;
__block __weak void (^weak_block)(NSString *);
void(^strong_block)(NSString *);
weak_block = strong_block = ^(NSString *str) {
void(^inner_block)(NSString *) = weak_block;
[self doSomethingWithCompletion:^{
NSLog(@"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
strong_block(testString);
}
Upvotes: 1
Reputation: 2823
Not sure this is proof of either possibility but if you add a weak
block property and examine that one after all block stuff has run...
...
@property (weak) void (^true_weak_block)(NSString *);
@property (weak) NSString *weak_string;
...
- (void) testBlocks {
NSString *strong_string = [NSString stringWithFormat:@"%@", @"some string"]; // note that you can not use a string literal in this example..
self.weak_string = strong_string;
NSString *testString = @"hello";
__block NSInteger count = 0;
__block void (^inner_block)(NSString *);
void(^strong_block)(NSString *);
inner_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(@"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
self.true_week_block = strong_block;
[self test];
strong_block(testString);
}
- (void)test {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", self.true_week_block); // not deallocated
NSLog(@"%@", self.weak_string); // deallocated
});
}
In my test the block is never deallocated and also the memory address remains the same over time, even if you change regular assignment of the two strong blocks to use copy instead of the implicitly retained assignment.
Upvotes: 0