美文网首页
Block总结

Block总结

作者: 王的for | 来源:发表于2018-11-14 22:26 被阅读0次

    一、Block的底层结构及本质

    (1)block本质:

    从代码可以看出,Block的本质就是NSObject. 也就是说block就是一个对象。

    (2)block结构

    利用clang将main.m文件编译为cpp文件。看下block底层实现的结构

    最终block是转成了__main_block_impl_0的结构。

    block结构体表示如下:

    Block结构

    二、Block的变量capture机制

    capture机制:为了保证block内部能够正常访问外部的变量。

    1、局部变量捕获:(会捕获到block内部成为新的结构体变量成员)

    (1)auto类型的直接捕获到block内部(值传递)。

    证明:

    再看下impl结构体的变化

    可以看到,age值进过构造函数直接值传递到impl结构体里面的age。

    (2)static 变量捕获是指针传递

    证明:

    2、全局变量捕获

    (1)全局变量在block里面是直接访问。(不会捕获到block内部产生新的结构体变量成员)

    证明:

    从截图可以看到,age在nslog打印时(block里面的任务被包装成函数),是直接访问拿来用的,并没有捕获进到block内部。

    (3)同理,全局的obj对象,也是不会捕获到block里面,如果是全局NSObject*obj对象就直接访问NSObject*obj,如果是static NSObject*obj对象,就是会把NSObject**obj作为传递进去使用(&obj传进去)。(二级指针)

    三、Block的类型

    Block类型有:__NSGlobalBlock__、__NSStackBlock__、__NSMallocBlock__最终都是继承自NSBlock类型,基类是NSObject。(可查看最上面的截图)

    1、__NSGlobalBlock__(存放在数据区):没有访问auto变量的block就是__NSGlobalBlock__;

    2、__NSStackBlock__(存放在栈段):访问了auto变量的block就是__NSStackBlock__;

    3、__NSMallocBlock__(存放堆段,需要程序猿管理):__NSStackBlock__调用copy后得到block就是__NSMallocBlock__;

    (1)ARC环境下证明

    (2)MRC环境下证明

    如果block2赋值的时候加上copy,则block会变成mallocBlock。

    说明:

    (1)ARC环境下,编译器会根据情况自动将栈上的block复制到堆上。比如:

            1、block作为函数返回值时

            2、将block赋值给__strong指针时

            3、block作为方法名含有usingBlock的方法参数时

            4、block作为GCD API的方法参数时

    (2)ARC下block属性的建议写法:

            1、@property(strong, nonatomic) void(^block)(void);

            2、@property(copy, nonatomic) void(^block)(void);

    (3)MRC下block属性的建议写法:@property(copy, nonatomic) void(^block)(void);

    四、block内部有对象类型的auto变量,内存管理注意点

    1、当block内部访问了对象类型的auto变量时:

    先看底层结构:

    main函数代码如下

    转cpp代码:

    结构体多了person指针

    结构体desc里面多了两个函数指针

    copy与dispose函数的作用:对block捕获的auto对象变量做内存管理,retain/release等操作,具体如果操作根据auto对象变量修饰符的方式进行。

    copy及dispose函数具体实现如下:

    (1)如果block是在栈上,将不会对auto变量产生强引用(or retain)

    MRC证明:

    ARC证明:

      (2)  如果block被拷贝到堆上

    1、会调用block内部的copy函数

    2、copy函数内部会调用_Block_object_assign函数

    3、_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

    4、如果block从堆上移除会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)

    说明:block捕获__weak与__strong的auto变量对象,学到这里就需要知道为什么_strong的会导致强引用了。(默认不修饰auto对象变量,隐藏了修饰符__strong。)

    五、__block修饰符

    可以用于解决block内部无法修改auto变量值的问题。

    block捕获进去的auto变量,是不允许被修改的。因为block的实现内容也是被包装成一个函数的调用。函数与函数直接是不能跨域访问auto变量的,auto变量属于对应的函数栈空间内。

    block可以修改全局变量跟static变量。

    那我们来看看给auto变量加上__block后,为什么就可以修改变量了。

    main函数的代码如下:

    转cpp后,看下__block int age的变化

    从底层本质看出,不再是一个简单的int age变量,而是经过编译器转换成了对象(结构体__Block_byref_age_0),结构体里面存放着int age这个成员(从这大概可以知道,为了改变int age的值,先包装成一个对象,利用结构体指针来修改成员int age值)。

    然后在block内部修改age的值,是通过age结构体指针去修改结构体成员age的值。以此达到了我们修改age值的目的。

    特别说明:

    1、__block int abc基本数据类型格式的捕获

    (1)如果block此时是在stack,属于stackBlock,此时block里面捕获__block int abc变量,此时结构体(__Block_byref_abc_0)是在栈中,当然结构体的age成员值也是在栈中。

    证明:

      特别说明:此处打印的abc地址是__Block_byref_abc_0结构体里面的成员int abc的地址。不是__Block_byref_abc_0结构体的地址。但是也可以说明__Block_byref_abc_0结构体目前是存在栈中。

    (2)如果block进过copy,生成的mallocBlock,里面捕获__block int abc变量,此时__Block_byref_abc_0结构体也会被copy在堆上。

    证明如下:

    小tips: 因为有个全局的block1强引用着block,也就意味着在其他函数中也可以用的这个block,所以__block int age也必须copy到堆中才行,这样才能保证全局block1(30)调用,可以修改abc的值。如果abc还在栈中,可能早就被其他数据覆盖或者函数执行完后成为了垃圾数据。

    (3)struct__main_block_impl_0结构体里面的copy及dispose函数管理由__block int abc生转换成的__Block_byref_abc_0的内存。

    编译cpp后得到底层代码如下:

    管理结构体__Block_byref_abc_0内存函数

    2、__block NSObject*obj对象类型的捕获

    (1)同样生成__Block_byref_obj_1的结构体

    与之前的__block int age不同的是,这个结构体里面多了两个函数copy,dispose.这个两个函数用来对结构体里面的obj对象成员做内存管理。

    3、__block的内存管理

    (1)当block在栈上时,并不会对__block变量产生强引用

    MRC环境证明:

    ARC环境证明:

    (2)当block被copy到堆时,会调用block内部的copy函数(copy函数内部会调用_Block_object_assign函数),_Block_object_assign函数会对__block auto变量形成强引用(retain),也会将__block auto变量拷贝到堆上。

    ARC证明

    (3)特殊请看,block被copy到堆时,捕获的__block obj局部auto对象MRC情况下有点特别,不会被retain

    请看下面代码

    内存管理指示图

    (4)当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)

    (5)__block的__forwarding指针妙用:当我们的block复制到堆中时,__block变量也会复制到堆中,这时在栈中的__block变量和堆中的__block变量的__forwarding指针都会指向堆中的__block变量。这样做的目的:即使你修改栈中的block内部的__block变量,也能达到修改到堆中__block变量,防止程序修改出错。

    六、解决循环引用

    1、ARC环境

    第三种利用__block打破循环示意图

    2、MRC环境

    六、总结

    1、Block本质是OC对象,基类也是NSObject

    2、Block分为:__NSGlobalBlock__、__NSStackBlock__、__NSMallocBlock__三种类型

    3、Block捕获auto变量(假设int age),会在Block结构体里面生成对应成员int age

    4、Block不会对全局变量(假设int tall)捕获,在block里面直接可以访问全局int tall,不会被捕获

    5、Block捕获局部static变量(假设static int height),会在Block结构体里面生成对应成员int *height(指针)

    6、当block在栈上时,对(对象类型的auto变量、__block变量)都不会产生强引用

    7、当block拷贝到堆上时,都会通过copy函数来处理它们

        (1)__block变量(假设变量名叫做a) 调用如下函数

    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

        (2)对象类型的auto变量(假设变量名叫做obj) 调用如下函数

    _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);

    8、当block从堆上移除时,都会通过dispose函数来释放它们

    (1)__block变量(假设变量名叫做a) 调用如下函数

    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

    (2)对象类型的auto变量(假设变量名叫做obj)

    _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);

    9、当__block变量被copy到堆时,会调用__block变量内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)

    10、如果__block变量从堆上移除会调用__block变量内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放指向的对象(release)

    11、ARC解决循环引用:(1)用__weak、__unsafe_unretained解决(2)用__block解决(必须要调用block)

    12、MRC解决循环引用:(1)__unsafe_unretained (2)__block

    相关文章

      网友评论

          本文标题:Block总结

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