《Block 原来你是这样的(二)》解析:**Clang**、**自动变量**、**self**
2023-12-07 07:16:32
Block的幕后机制:Clang下的内部运作
在上一篇文章中,我们了解了__block
说明符如何让截获的变量在Block的作用域外依然有效。这要归功于Clang编译器,它在后台创建了一个名为__block_impl
的结构体。
__block_impl结构体:Block变量的幕后推手
__block_impl
结构体包含了以下字段:
isa
: 指向该结构体的类对象。Flags
: 一个标志位,记录变量的属性,如可变性、可截获性等。Reserved
: 保留字段,目前未使用。FuncPtr
: 指向变量析构函数的指针。
当使用__block
声明一个变量时,Clang编译器会自动生成一个__block_impl
结构体。这个结构体包含了变量的值以及它在栈上的地址。即使变量超出了其作用域,但仍然可以通过__block_impl
结构体访问其值。
自循环引用的陷阱:self
变量的双刃剑
Block中经常用到的self
变量是一个指向当前对象的指针。如果在Block中使用了self
变量,那么该Block必须截获当前对象。这会导致Block和当前对象之间形成一个循环引用。
@interface MyClass : NSObject
{
__block int x;
}
@end
@implementation MyClass
- (void)test
{
__block int y = 10;
void (^block)(void) = ^{
NSLog(@"%d, %d", x, y);
};
// ...
}
@end
在这个例子中,test
方法中的Block截获了self
变量和y
变量。这样一来,Block和MyClass
对象之间就形成了一个循环引用。当MyClass
对象被释放时,Block也会被释放。但是,Block又截获了MyClass
对象,所以MyClass
对象无法被释放。这就会导致内存泄漏。
为了避免循环引用,可以在Block中使用__weak
修饰符来修饰self
变量。这样,Block就不会截获self
变量,从而避免循环引用。
@interface MyClass : NSObject
{
__weak id self;
}
@end
@implementation MyClass
- (void)test
{
__block int y = 10;
void (^block)(void) = ^{
NSLog(@"%d, %d", self.x, y);
};
// ...
}
@end
在这个例子中,test
方法中的Block使用__weak
修饰符来修饰self
变量。这样,Block就不会截获self
变量,从而避免了循环引用。
结论:在Block中谨慎使用self
在Block中使用self
变量时要小心,因为这可能会导致循环引用和内存泄漏。通过使用__weak
修饰符,可以避免这种情况,确保Block在超出了其作用域后也能正常释放。
常见问题解答
-
为什么
__block_impl
结构体中会有一个FuncPtr
字段?
答:FuncPtr
字段指向变量的析构函数,当变量超出其作用域时,该析构函数将被调用。 -
为什么使用
__weak
修饰符可以避免循环引用?
答:__weak
修饰符不会截获self
变量,因此Block就不会持有对self
对象的强引用。 -
如何判断一个Block是否截获了当前对象?
答:可以使用Block_copy
函数,它会返回一个新Block,其中包含了对当前对象的强引用。如果新Block和原始Block的值不同,则说明原始Block截获了当前对象。 -
如果一个Block截获了多个变量,是否会产生多个
__block_impl
结构体?
答:是的,每个被截获的变量都会生成一个单独的__block_impl
结构体。 -
__block
变量可以在Objective-C++中使用吗?
答:是的,__block
变量可以在Objective-C++中使用,但需要遵守特定的语法规则。