Block总结

一、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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 要理解Block的实现,要先理解runtime,然而理解Runtime要先理解C语言的结构体(可见我基础是TM有多...
    Kiven_Berry阅读 446评论 0 0
  • 参考 Block编译代码解读:block没那么难(一、二、三)iOS进阶——iOS(Objective-C) 内存...
    啊哈呵阅读 805评论 0 3
  • block的本质 block本质上也是一个OC对象,它内部也有个isa指针block是封装了函数调用以及函数调用环...
    斑驳的流年无法释怀阅读 280评论 0 2
  • 上一篇文章iOS底层原理总结 - 探寻block的本质(一)中已经介绍过block的底层本质实现以及了解了变量的捕...
    二斤寂寞阅读 589评论 0 1
  • 推荐指数: 6.0 书籍主旨关键词:特权、焦点、注意力、语言联想、情景联想 观点: 1.统计学现在叫数据分析,社会...
    Jenaral阅读 5,700评论 0 5