Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-c recursive blocks with threads EXC_BAD_ACCESS

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?

like image 698
richy Avatar asked Nov 09 '22 08:11

richy


1 Answers

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);
}
like image 71
newacct Avatar answered Nov 15 '22 05:11

newacct