美文网首页
Block 与 Closure

Block 与 Closure

作者: lsh_01 | 来源:发表于2019-02-22 19:47 被阅读0次

    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

          本文链接:https://www.haomeiwen.com/subject/ltxeyqtx.html