Objective-C递归块与线程EXC_BAD_ACCESS问题

5

我有一些objective-c中的递归块代码,导致出现EXC_BAD_ACCESS错误。

- (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);
}

错误发生在 weak_block(str) 上,我认为这是因为在调用 dispatch_async 时它被释放了。在声明为 __block 时,可以将其替换为 strong_block(str)。请参考以下内容:
__block void(^strong_block)(NSString *);

出现警告“在此块中强烈捕获 'strong_block' 可能会导致保留循环”。

因此,我将 testBlock 方法更改为不使用弱引用,如下所示:

- (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);
}

但我不确定这是否会导致保留循环,或者是否添加

__block void (^inner_block)(NSString *) = weak_block;

如果在块中使用self而不是weak self,也会导致保留循环。那么处理这种情况的正确方法是什么?


1
我看这个东西看得脑袋疼。 - Peter Segerblom
2个回答

1
它崩溃的原因是块(由weak_blockstrong_block指向)在“完成”块运行时已被释放,调用带有nil块指针的块会导致崩溃。
该块被释放,因为testBlocks返回后没有强引用它。
第二个将产生保留周期,因为该块捕获了inner_block,它持有对自身的强引用。
正确的方法是从块内捕获的弱引用创建一个强引用,并让完成块捕获该引用:
- (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);
}

这对我有用,我的错误是我使用了:typeof(weak_block) inner_block = weak_block; 而不是 void(^inner_block)(NSString *) = weak_block; 这使它变得弱而不是强。 - malhal

0

不确定这证明了哪种可能性,但如果您添加一个weak块属性并在所有块内容运行后检查该属性...

...
@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
    });
}

在我的测试中,该块从未被释放,即使您将两个强块的常规赋值更改为使用复制而不是隐式保留的赋值,内存地址也会随时间保持不变。

嘿,我认为作为类的属性的 true_weak_block 不会被释放,直到该类被释放对吗?还是我自己搞混了呢? - richy
如果它的行为像一个普通对象,那么它将按预期工作,因为将弱引用分配给它不会增加引用计数,所以当原始引用超出作用域时,它将被释放。 - Peter Segerblom
更新了一个使用普通对象的测试。 - Peter Segerblom
没错!但是将 self.true_weak_block(str); 放在 inner_block(str); 的位置仍会导致 EXC_BAD_ACCESS 错误。 - richy
是的,看起来编译器注意到它没有在块内使用,因此不将其放入块存储中,或者它在块内未被使用,使其无法保留。如果您只是放置一个检查 if(inner_block){self.true_weak_block(str);} 它就像以前一样“工作”。 - Peter Segerblom
如果您删除块内对任何强引用的引用,则在测试方法运行时,true_weak_block将为 nil - Peter Segerblom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接