Objective c iOS:访问时直接设置的块属性崩溃

Objective c iOS:访问时直接设置的块属性崩溃,objective-c,ios,automatic-ref-counting,clang,Objective C,Ios,Automatic Ref Counting,Clang,考虑以下代码: @interface ClassA : NSObject @property (nonatomic, copy) void(^blockCopy)(); @end @implementation ClassA @synthesize blockCopy; - (void)giveBlock:(void(^)())inBlock { blockCopy = inBlock; } @end 然后在一个名为someA的类中使用它,该类具有类型为ClassA的stron

考虑以下代码:

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

@implementation ClassA

@synthesize blockCopy;

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

@end
然后在一个名为
someA
的类中使用它,该类具有类型为
ClassA
strong
属性:

self.someA = [[ClassA alloc] init];
[self.someA giveBlock:^{
    NSLog(@"self = %@", self);
}];
dispatch_async(dispatch_get_main_queue(), ^{
    self.someA.blockCopy();
    self.someA = nil;
});
如果我在iOS上启用ARC运行构建的
O3
,它会在
self.someA.blockCopy()期间崩溃内部调用
objc\u retain
。为什么?

现在我意识到,人们可能会说我应该将其设置为
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\u storeStrong
,它依次在块上执行
保留
,在旧块上执行
释放
。我的猜测是ARC没有正确地注意到它是一个块属性,因为我认为它应该调用
objc_retainBlock
,而不是正常的
objc_retain

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

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

注意事项:

  • 它不会在OSX上崩溃
  • 它不会崩溃生成
    O0
您需要在赋值时或传入此函数时复制块。虽然ARC解决了返回时自动移动到堆的问题,但它对参数不这样做(不能处理C的特性)

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


我仍然不太明白为什么它不能做objc_ 而不是一个普通的对象。编译器知道类型 毕竟

我很确定,问题在于任务的潜在成本。如果块捕获了很多状态,包括潜在的其他块,那么块拷贝()可能非常昂贵

例如,如果您有以下情况:

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

。。。这就意味着仅仅因为赋值就需要Block_copy(),这就不可能在没有病态性能问题风险的情况下一致地使用块。因为编译器无法知道
SomeRandomFunc()
是同步的还是异步的,所以无法自动管理这一点(在这个时候——我确信消除这种潜在的tripwire是可取的).

我只是有点惊讶地看到它在赋值时通过了一个
objc\u storeStrong
,它不能“做正确的事情”。据我所知(已经有一段时间了),有一些边缘情况会阻止编译器发出在所有情况下都“正常工作”的代码。在ARC下,沙中的强硬路线要求编译器能够准确地证明任何给定的代码模式在任何地方都始终有效。在这种情况下,它不能这样做,因为通过任何给定调用站点(包括
objc\u storeStrong()
)传递的仅堆栈块参数都有有效的用途。不过,我仍然不太明白为什么它不能执行
objc\u blockRetain
,而不是正常的
objc\u retain
。编译器毕竟知道类型。我喜欢你的更新,但在我看来那是不同的。那是在函数调用期间。我说的是设置一个类的实例变量。编译器可以知道我们希望稍后使用它,除非我们在设置它的方法末尾将它设置回
nil
,在这种情况下,它将
保留
,并
释放
,这将取消它,ARC可以优化它。当然还有改进的空间。问题仍然是,即使使用基本赋值,也可能有其他代码路径以不安全的方式使用块。这里有大量的边缘案例。我怀疑这会随着时间的推移变得更好;当前的行为是最不意外的模式——它崩溃或在可预测的时间内工作。
BlockType b = ^(...) { ... capture lots of gunk ... };
SomeRandomFunc(b);