Objective-C Block中实例变量信号量的操作疑问与Apple实现解析
咱们一个个来拆解你的问题:
1. Apple把实例信号量复制为__block局部变量的实现是否必要?
绝对有必要,而且是规避循环引用和保证内存安全的关键操作。
你得明白,当Block里直接访问实例变量_inFlightSemaphore时,Block实际上捕获的是self(因为实例变量本质是通过self->_inFlightSemaphore来访问的)。Metal的渲染回调Block会被提交到GPU队列,可能在self被释放后才执行——如果self持有MTKView或相关渲染资源,就会形成self → 渲染资源 → Block → self的循环引用,导致self无法被正确释放,进而造成内存泄漏。
把实例变量复制成__block修饰的局部变量后,Block捕获的是这个局部的信号量指针,而非self。这样既切断了循环引用的链条,又能保证Block执行时能正确访问到信号量(这类信号量通常和实例生命周期绑定,复制后也能保证有效性)。
2. 直接访问实例变量的方式是否可行?
短期跑Demo可能没问题,但存在严重的内存安全隐患。
如果你的测试代码生命周期很短,可能看不到问题,但在长期运行的应用中,循环引用会导致内存泄漏,self对应的对象永远无法被释放。极端情况下,要是self在Block执行前就被释放了,_inFlightSemaphore会变成野指针,调用dispatch_semaphore_signal直接就会崩溃。
所以直接访问实例变量虽然能“正常运行”,但属于不安全的写法,Apple的示例是在教你正确的、无隐患的实现方式。
3. 为何Clang允许给实例变量加__block而GCC不允许?
这是因为__block是Apple为Objective-C扩展的语法特性,而GCC对Objective-C的支持远不如Clang(毕竟Clang是Apple主导开发的,专门适配Apple生态的各种语言特性)。
原本__block的设计是用来修饰局部变量的,目的是让局部变量可以在Block中被修改(突破Block默认把局部变量捕获为const副本的限制)。但Clang额外做了扩展,允许用__block修饰实例变量,本质上是让实例变量在Block中被访问时,不通过self捕获,而是直接捕获实例变量的内存地址(类似局部变量的处理逻辑)。
而GCC没有实现这个扩展特性,它严格遵循__block的原始设计,只允许修饰局部变量,所以遇到给实例变量加__block的写法就会报错。
内容的提问来源于stack exchange,提问作者ackh

