Block

作者: 陈_振 | 来源:发表于2018-05-27 18:50 被阅读0次

    block- 三种block__NSGlobalBlock__ NSStackBlock __NSMallocBlock __

    自己测试的数据:
    测试时在buildSetting中将ARC关掉,因为在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上。

        /****** __NSGlobalBlock__ (没有访问auto变量)******/
    
        // 什么参数也不引用
        NSLog(@"%@",^{NSLog(@"*******");} );
        
        // 引用全局变量或者全局静态变量
        NSLog(@"%@", ^{NSLog(@"%d, %d", a, global_i);});
        
        // 引用局部静态变量
        NSLog(@"%@", ^{NSLog(@"%d", static_local_k);});
        
        // 被强指针引用,并且只引用了全局变量或静态变量
        strongBlock = ^{NSLog(@"%d", a);};
        NSLog(@"%@", strongBlock);
    
        /******__NSStackBlock (访问了auto变量)******/
    
        // 只引用局部变量
        NSLog(@"%@", ^{NSLog(@"%d%d%d", a, static_local_k, static_global_j);});
        // 引用局部变量和全局变量(或静态变量)
        NSLog(@"%@", ^{NSLog(@"%d, %d", a, global_i);});
        
        
        /****** __NSMallocBlock (__NSStackBlock 执行copy操作)******/
    
        // 被强指针引用,并且引用了局部非静态变量
        strongBlock = ^{NSLog(@"%d%d", a, global_i);};
        NSLog(@"%@", strongBlock);
        
        // 被强指针引用,并且引用了局部非静态变量,和全局变量(或静态)
        strongBlock = ^{NSLog(@"%d%d", a, global_i);};
        NSLog(@"%@", strongBlock);
    

    总结:

    NSGlobalBlock(不持有对象)

    以下总结基于MRC条件下,因为在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上:

    1. 只要不引用auto变量,那么这个block就是__NSGlobalBlock

    NSStackBlock(不持有对象)

    1. 只要引入auto变量,那么这个block就是__NSStackBlock

    NSMallocBlock(持有对象)

    1. __NSStackBlock 执行copy操作。

    Block的副本:

    Block的类 副本源的配置存储域 复制效果
    __NSConcreteStackBlock 从栈复制到堆
    __NSConcreteGlobalBlock 程序的数据区(全局区) 什么也不做
    __NSConcreteMallocBlock 引用计数增加

    不管block配置在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。(浪费CPU资源)

    block的copy

    在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上(即执行copy操作),比如以下情况:

    • block作为函数返回值时
    • 将block赋值给__strong指针时
    • block作为Cocoa API中方法名含有usingBlock的方法参数时
    • GCD中用到的block参数

    当block内部访问了对象类型的auto变量

    无论是ARC还是MRC,

    1. NSStackBlock(即block在栈上)中访问对象类型的自动变量时,block不会持有该对象。
    2. NSMallocBlock(即block被copy到堆上)中访问对象类型的自动变量时,block会持有该对象,即该对象的引用计数会加1.

    分析:
    如果block被拷贝到堆上,
    首先会调用block内部的copy函数,
    然后copy函数内部会调用_Block_object_assign函数,
    _Block_object_assign函数会根据auto变量的修饰符(__strong,__weak,__unsafe_unretained)做出相应的操作,类似retain(形成强引用,弱引用)

    Screen Shot 2018-05-27 at 15.36.32.png

    如果block从堆中移除:
    会调用block内部的dispose函数,
    dispose函数内部会调用_Block_object_dispose函数,
    _Block_object_dispose函数会自动释放引用的auto变量,类似于release

    在block内修改外部变量的方式

    1. 静态变量可以在block内直接修改
    2. 全局变量可以在block内直接修改
    3. 使用__block修饰符修饰auto变量,可以在block内修改

    __block修饰符

    • __block可以用于解决block内部无法修改auto变量值的问题
    • __block不能修饰全局变量,静态变量(static)
    • __block 会将变量包装成一个对象
    Screen Shot 2018-05-27 at 17.01.04.png

    __block的内存管理

    • 当block在栈上时,并不会对__block变量产生强引用
    • 当block被copy到堆上时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数会对__block变量形成强引用
      Screen Shot 2018-05-27 at 18.27.16.png
    • 当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)
      Screen Shot 2018-05-27 at 18.30.17.png

    Block被复制到堆上时,__block变量也被复制到堆上,被Block持有。
    __block变量同时被两个Block持有时,会增加__block的引用计数。

    总结-对象类型的auto变量和__block变量

    Screen Shot 2018-05-27 at 18.47.45.png

    被__block修饰的对象类型(对象类型😯)

    • 当__block变量在栈上时,不会对指向的对象产生强引用
    • 当__block变量被copy到堆上时,会调用__block变量内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据所指向对象的修饰符(__strong,__weak,__unsafe_unretained)做出相应的操作,类似retain(形成强引用,弱引用)(!!!:这里仅限于ARC时会retain,MRC时不会retain)

    在MRC下:(如下图)block还没释放,person对象就dealloc了

    Screen Shot 2018-05-27 at 20.03.30.png

    __block变量结构体中__forwarding指针的作用

    Screen Shot 2018-05-27 at 18.56.37.png

    先给个结论:
    __forwarding指向__block变量本身。当__block变量在栈上的时候,__forwarding指向__block在栈上的地址;当__block被复制到堆上后,栈上__block变量的__forwarding指向__block在堆上的地址,同样,堆上__block变量的__forwarding指针也指向它在堆上的地址,这样,当在函数中改变__block变量时,访问的便是同一个__block变量。细节看下面代码。

        typedef void (^blk_t)(void);
        
        __block int val = 0;
        
        
        blk_t blk = [^{++val;} copy];    // 可转化为++(val.__forwarding->val)
        
        ++val;     // 可转化为++(val.__forwarding->val)
        
        blk();
        NSLog(@"val:%d", val);
    

    上述代码执行后val的值变为2


    Screen Shot 2018-03-19 at 14.25.35.png

    block中引用self时会不会造成循环引用

    • 首先考虑block的类型,__NSStackBlock__不会持有对象,所以不会造成循环引用;如果是__NSMallocBlock__,则会持有对象,所以会导致循环引用。

    解决循环引用-ARC

    三种方法:

    Screen Shot 2018-05-27 at 20.52.32.png

    解决循环引用-MRC

    Screen Shot 2018-05-27 at 21.03.42.png

    参考文章

    相关文章

      网友评论

          本文标题:Block

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