返回

《Block 原来你是这样的(二)》解析:**Clang**、**自动变量**、**self**

IOS

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在超出了其作用域后也能正常释放。

常见问题解答

  1. 为什么__block_impl结构体中会有一个FuncPtr字段?
    答:FuncPtr字段指向变量的析构函数,当变量超出其作用域时,该析构函数将被调用。

  2. 为什么使用__weak修饰符可以避免循环引用?
    答:__weak修饰符不会截获self变量,因此Block就不会持有对self对象的强引用。

  3. 如何判断一个Block是否截获了当前对象?
    答:可以使用Block_copy函数,它会返回一个新Block,其中包含了对当前对象的强引用。如果新Block和原始Block的值不同,则说明原始Block截获了当前对象。

  4. 如果一个Block截获了多个变量,是否会产生多个__block_impl结构体?
    答:是的,每个被截获的变量都会生成一个单独的__block_impl结构体。

  5. __block变量可以在Objective-C++中使用吗?
    答:是的,__block变量可以在Objective-C++中使用,但需要遵守特定的语法规则。