Block 与 Closure

Block In OC

block 分为以下三种:

  • _NSConcreteStackBlock:栈block,引用了自动变量的block;
  • _NSConcreteMallocBlock:堆block,栈block执行copy得到的block;
  • _NSConcreteGlobalBlock:全局block,没有引用自动变量的block。

捕获规则:

  1. 捕获动作发生在创建block时;
  2. 对于全局变量(静态/非静态):在作用域中,不需要捕获;
  3. 对于静态变量(非全局):引用拷贝;
  4. 对于自动变量:值拷贝;
  5. 对于block变量(__block只能修饰自动变量):将变量包装成为结构体,引用拷贝该结构体。

示例代码:

typedef void(^Block)(void);

static int a = 1;

@implementation TestBlock

- (void)test {
    static int b = 1;
    int c = 1;
    __block int d = 1;
    NSObject *e = [NSObject new];
    __weak NSObject *f = e;
    __block NSObject *g = [NSObject new];
    
    Block block = ^{
        a = 2;
        b = 2;
        //c = 2; // error: Variable is not assignable
        printf("c = %d", c);
        d = 2;
        printf("e -> %p", e);
        printf("f -> %p", f);
        printf("f -> %p", g);
    };
    
    block();
}

@end

等效于下面的代码:

typedef void(^Block)(void);

static int a = 1;

struct _struct_d { int d; };
struct _struct_g { NSObject *g; };

void _impl_block(int *b, const int c, struct _struct_d *d,
                 __strong NSObject *e, __weak NSObject *f,
                 struct _struct_g *g) {
    a = 2; // a为全局变量,可以直接访问
    *b = 2; // b为静态变量,引用拷贝
    //c = 2; //error
    printf("c = %d", c); // c为自动变量,值拷贝,并且拷贝后的变量为常量
    (*d).d = 2; // d为 __block 修饰的自动变量,将d包装为 struct _struct_d *,然后拷贝该指针
    printf("e -> %p", e);
    printf("f -> %p", f);
    printf("f -> %p", g);
}

struct _struct_block {
    // 引用计数器。初始化之后引用计数为1;每增加一个引用时计数器+1;每减少一个引用时计数器-1;计数器为0时触发 free。
    int _retainCount; 
    int *b;
    const int c;
    struct _struct_d *d;
    __strong NSObject *e;
    __weak NSObject *f;
    struct _struct_g *g;
    void (* _impl_block)(int *, const int,
                         struct _struct_d *, __strong NSObject *,
                         __weak NSObject *, struct _struct_g *);
};

@implementation TestBlock

- (void)test {
    static int b = 1; 
    int c = 1;
    // 将d转为指向堆区内存的指针(该堆区内存的管理未给出)
    struct _struct_d *d = (struct _struct_d *)malloc(sizeof(struct _struct_d));
    (*d).d = 1;
    __strong NSObject *e = [NSObject new];
    __weak NSObject *f = e;
    // 将g转为指向堆区内存的指针(该堆区内存的管理未给出)
    struct _struct_g *g = (struct _struct_g *)malloc(sizeof(struct _struct_g *));
    (*g).g = [NSObject new];
    
    struct _struct_block block = { 
        1, // 初始化 block 后,引用计数为 1
        &b, // 静态变量不会被释放,引用拷贝即可
        c, // 普通变量,值拷贝
        d, // 指向堆区内存的指针,值拷贝该指针即可
        e, // 指针,值拷贝;由于是 __strong 类型,对象的引用计数会增加
        f, // 指针,值拷贝;由于是 __weak 类型,对象的引用计数不变
        g, // 指针,值拷贝;拷贝 struct _struct_g 的指针不会影响到 NSObject 对象的引用计数
        _impl_block // 目标函数
    }; // 拷贝变量,发生在创建 block 时;由于引用了自动变量,所以该 block 是栈 block。
    
    // 执行 block,不需要从上下文中获取变量
    block._impl_block(block.b, block.c, block.d, block.e, block.f, block.g); 
    
    block._retainCount --;
}

@end

总结:

  1. 栈block的copy得到堆block,堆block和全局block的copy返回自身;
  2. 在ARC中,赋值操作会触发栈block的copy,所以在ARC中大多数block是堆block;
  3. 在MRC中block捕获的OC对象指针会触发该对象的retain操作,防止出现这个现象的方法是:用__block修饰OC对象指针,将指针包装为结构体,copy操作不会复制该结构体;
  4. 在ARC中block捕获OC对象指针时,内部的指针会继承该指针的(__strong/__unsafe_unretained/__weak)修饰符,所以防止block强引用捕获的OC对象的方法是:用__weak获取一个弱引用OC对象的指针,在block使用__strong获取一个强引用OC对象的指针,防止block执行过程中OC对象被释放。

将OC编译为C++的指令:

clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations block_arc.m -o block_arc.cpp

Closure In Swift

捕获规则:

  1. 捕获动作发生在创建closure时,捕获动作分为直接捕获和列表捕获;
  2. 直接捕获:引用拷贝,类似OC中__block int__block NSObject *的做法;
  3. 列表捕获非class:值拷贝,类似OC中int的做法;
  4. 列表捕获class,拷贝对象的指针。根据使用的前缀不同,分为以下情况:
    • 没有前缀:得到对象的__strong指针,对象引用计数+1;
    • unowned前缀:得到对象的__unsafe_unretained指针,对象引用计数不变;
    • weak前缀:得到对象的__weak指针,对象引用计数不变。

函数转变为 closure:

函数被变量引用时,会自动转变为闭包,并且执行捕获动作。
如果函数是实例的成员函数,除常规捕获外,还会捕获该实例。
这是容易引发循环引用的原因之一。

你可能感兴趣的:(Block 与 Closure)