【问题标题】:iOS: Block property directly set crashes when accessediOS:Block属性直接设置访问时崩溃
【发布时间】:2012-02-08 03:31:27
【问题描述】:

考虑以下代码:

@interface ClassA : NSObject
@property (nonatomic, copy) void(^blockCopy)();
@end

@implementation ClassA

@synthesize blockCopy;

- (void)giveBlock:(void(^)())inBlock {
    blockCopy = inBlock;
}

@end

然后在具有ClassA 类型的strong 属性称为someA 的类中使用它:

self.someA = [[ClassA alloc] init];
[self.someA giveBlock:^{
    NSLog(@"self = %@", self);
}];
dispatch_async(dispatch_get_main_queue(), ^{
    self.someA.blockCopy();
    self.someA = nil;
});

如果我在启用 ARC 的情况下运行构建的 O3,在 iOS 上,它会在 objc_retain 内部的 self.someA.blockCopy(); 调用期间崩溃。为什么?

现在我意识到人们可能会说我应该使用self.blockCopy = inBlock 进行设置,但我确实认为 ARC 应该在这里做正确的事情。如果我查看由giveBlock: 方法生成的程序集(ARMv7),它看起来像这样:

        .align  2
        .code   16
        .thumb_func     "-[ClassA giveBlock:]"
"-[ClassA giveBlock:]":
        push    {r7, lr}
        movw    r1, :lower16:(_OBJC_IVAR_$_ClassA.blockCopy-(LPC0_0+4))
        mov     r7, sp
        movt    r1, :upper16:(_OBJC_IVAR_$_ClassA.blockCopy-(LPC0_0+4))
LPC0_0:
        add     r1, pc
        ldr     r1, [r1]
        add     r0, r1
        mov     r1, r2
        blx     _objc_storeStrong
        pop     {r7, pc}

这就是调用objc_storeStrong,它反过来在块上执行retain,在旧块上执行release。我的猜测是 ARC 没有正确注意到它是一个块属性,因为我认为它应该调用 objc_retainBlock 而不是正常的 objc_retain

或者,我完全错了,实际上 ARC 正在做它记录的事情,而我只是以错误的方式阅读它?

非常欢迎对此进行讨论 - 我觉得这很有趣。

注意事项:

  • 它不会在 OS X 上崩溃。
  • O0 构建时不会崩溃。

【问题讨论】:

    标签: objective-c ios automatic-ref-counting clang


    【解决方案1】:
    - (void)giveBlock:(void(^)())inBlock {
        blockCopy = inBlock;
    }
    

    您需要在赋值时或在传递到此函数时复制块。虽然 ARC 解决了 auto-move-to-heap-on-return 问题,但它并没有解决参数问题(不能解决 C 的特性)。

    在某些环境下它不会崩溃只是巧合;只要块的堆栈版本没有被覆盖,它就不会崩溃。一个明确的迹象是当您关闭优化后发生崩溃时。关闭优化后,编译器将不会在任何给定范围内重用堆栈内存,从而导致内存在应有的很长时间内“有效”。


    我还是不太明白为什么它不能做 objc_blockRetain 但是,而不是普通的 objc_retain。编译器知道类型 毕竟。

    我很确定问题在于分配的潜在成本。如果块捕获了很多状态,可能包括其他块,那么 Block_copy() 可能真的真的 真的昂贵。

    即如果你有类似的东西:

    BlockType b = ^(...) { ... capture lots of gunk ... };
    SomeRandomFunc(b);
    

    ... 这意味着 Block_copy() 仅仅因为分配,这将使得不可能在没有病理性能问题的风险的情况下一致地使用块。因为编译器无法知道SomeRandomFunc() 是同步的还是异步的,所以无法自动管理它(目前——我确信摆脱这个潜在的绊线是可取的)。

    【讨论】:

    • 我只是有点惊讶地看到它在分配时通过objc_storeStrong,但它无法“做正确的事情”。
    • 据我了解(已经有一段时间了),有一些边缘情况会阻止编译器发出在所有情况下都能正确“正常工作”的代码。在 ARC 下,沙中的硬线要求编译器能够准确地证明任何给定的代码模式将始终在任何地方始终有效。在这种情况下,它不能这样做,因为通过任何给定调用站点(包括objc_storeStrong())传递的仅堆栈块参数都是有效的。
    • 我仍然不完全理解为什么它不能做objc_blockRetain而不是正常的objc_retain。毕竟编译器知道类型。
    • 我喜欢你的更新,但那是不同的 IMO。那是在函数调用期间。我说的是设置类的实例变量。编译器可以知道我们稍后要使用它,除非我们在设置它的方法结束时将它设置回nil,在这种情况下它会retainrelease 它会取消出来,ARC 可以优化它。
    • 当然还有改进的余地。问题仍然存在,即使使用基本分配,也可能有其他代码路径最终以不安全的方式使用 Block。有很多边缘情况。我怀疑这会随着时间的推移而变得更好。当前行为是最不意外的模式——它崩溃或在可预测的时间内工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-01
    • 2018-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-06
    相关资源
    最近更新 更多