使用 Objective-C 块作为 @synchronized 锁

Using Objective-C block as a @synchronized lock

我有一个块 属性 定义如下:

@property (nonatomic, strong) void(^block)(void);

然后我尝试将其用作 @synchronized 锁:

@synchronized (self.block)
{
    //doing something
}

编译器不喜欢这样。它说 @synchronized requires an Objective-C object type ('void (^)(void)' invalid)

所以我通过将块转换为 id

来欺骗编译器
@synchronized ((id)self.block)
{
    //doing something
}

它似乎有效,但我不确定我做的是否正确。可能有一些我不知道的注意事项。你怎么看待这件事?使用这样的块是否可以接受,或者由于我不知道的某些原因,这是一种不好的做法?

您可以同步 NSObject 和子类。

在某个阶段深入到一个块变成 __NSMallocBlock____NSStackBlock__ 但这不是你应该依赖甚至真正意识到的东西。

我认为您需要有一个单独的对象作为锁。你为什么不做

@property (nonatomic,strong) NSObject * lock;

并在需要同步时同步。此外,您可以在设置块时设置它,例如

-(void)setBlock:(...)block
{
    _block = block;
    self.lock = NSObject.new;
}

你准备好了。

编辑

看到这个 NSDictionary and Objective-C block quirk - 小心块。

编译器优化了它们的存储方式,这在某些情况下可能会导致麻烦。如果你在一个块上同步,那么宁愿做类似

的事情
__strong NSObject * p = block;
@synchronize ( p )
{
    // ....
}

确保您和编译器就同步的内容达成一致。这里的 __strong 有点矫枉过正,你可以省略它,但只是明确说明你为什么需要它。

我认为,在实践中,做您正在做的事情应该是安全的。 Objective-C 块是 NSObject 的实例(虽然这是一个实现细节),并且您通常可以用一个块做任何您可以用任何其他对象做的事情。

NSStackBlocks 有时会有些棘手的事情,它表示捕获变量的块,这些变量在第一次复制之前最初驻留在堆栈中。它们很棘手,因为您必须复制它们,而不仅仅是保留它们,以便保留它们供以后使用。但是,在您的情况下,您正在处理存储在 属性 中的块。如果您正确编写代码,则必须在存储之前复制存储在 属性 中的块(因为 属性 可能比堆栈帧长)。 属性 与 copy 合成,或者您通过 getter 和 setter 实现了 属性,在这种情况下,当您将某些内容分配给块类型的实例变量时,ARC 应该复制它。 (如果您使用的是 MRC,那么您必须确保在将其存储在实例变量中之前复制它。)因此,假设它被复制,它必须是一个 NSGlobalBlock(表示不捕获变量的块)或 NSMallocBlock(表示捕获已复制变量的块)。这两个都像普通的 Objective-C 对象一样,只要有强引用就一直有效。

我能想到的唯一警告是,如果它是一个 NSGlobalBlock(即它是一个不捕获变量的块),那么对于该块的所有用途只有一个块对象。因此,在该块的不同实例上进行同步将共享相同的锁,这可能比您预期的更强大。