Block内存管理

__block修饰符所干的事

我们在经常会需要在block修改外部变量,而变量是值传递的时候,我们在block里是无法修改的。

int age = 10; 这里我们声明的变量默认是auto类型。

void (^block)(void) = ^{ age = 10;  }这是错误的,不能在block里修改外部的变量,block捕获外部变量,对于auto类型的局部变量,是值捕获,block里的a只是复制了a的值。我们可以去访问,但是不能修改

针对这种情况,我们在变量前加个__block 修饰符,__block int age = 10;这样就能达到在block里修改变量的目的了。

然而__block到底做了什么,起到了可以把外部值传递进来的对象在里面进行修改呢?如下图,写段测试代码

__block 修饰基础数据类型:

图-01

然后利用终端clang编译器,重新编译我们这个文件成C++文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64-arc.cpp.

在沙盒里会生成一个main-arm64-arc.cpp文件,这个文件与系统编译时,编译出来的文件是类似的,只是细节有些地方会不同。

我们可以看到 图-02 里与OC代码对应的这块,是声明的__Block_byref_age_0 对象而不是int age。然后声明block的地方,红色区域把这个变量传了进去,调用__main_block_impl_0(这个结构体便是block在C++的表现形式,block底层原理的文章有具体说明)的构造函数返回这个结构体的指针。__main_block_impl_0里如 图-03 所示,有一个__Block_byref_age_0成员,再接着看这个__Block_byref_age_0是什么东西,

图-02

我们的block结构体里会存着这个包装__block修饰的变量结构体,这个结构体是什么东西呢,如图-04所示。

图-03

这个__Block_byref_age_0里的age便是我们在外面的变量。

图-04

如何证明这个age便是外面的age呢,我们可以模拟这个文件的几个结构体,强转block对象,若如之前所说,被强转的block里面肯定有上图的几个属性。如下代码:

图-05

这里最后打印的确实是同一个地址。

如果__block修饰的是对象类型的变量,还会在__main_block_desc_0里生成两个函数,这两个函数,copy在block由栈空间搬到堆空间时被调用,disoise在block被销毁时调用。

补充1:__main_block_impl_0里对__Block_byref_age_0结构体成员在ARC环境永远下是强引用,在__Block_byref_age_0结构体里,外面捕获进来的变量如果是对象才会有强弱引用之分,在MRC情况下是弱引用,MRC编译环境的情况下,在解决循环引用的问题上,可以用__block修饰符,也能解决循环引用的问题。

补充2:在block代码块封装的函数里,访问这个外面捕获的__block所修饰的变量是通过age->__forwarding->age这样的形式,其中第一个age 是__Block_byref_age_0结构体,__forwarding是指向__Block_byref_age_0结构体自己的指针,再通过指向自己的指针访问真正age。为什么不直接通过结构体age->age呢,因为,在函数里,block默认是在栈的,但是ARC环境下,系统会默认对blockcopy搬到堆上,而block结构体里的__block变量还是在栈上,这样在堆上的__Block_byref_age_0结构访问栈上的__block变量就会存在危险,所以弄了个__forwarding指针,他是指向堆上的__Block_byref_age_0结构体,这样的话,不管这个__Block_byref_age_0是在堆上还是在栈上的,里面的__forwarding指针都是指向堆里的__Block_byref_age_0结构体自己。

2:__block修饰对象型auto变量

block捕获了对象,__main_block_impl_0结构体里的成员__main_block_desc_0结构体里就会多两个函数:copy和dipose。当block从栈上拷贝到堆上的时候,就会调用copy函数,对所有的__Block_byref_obj_1变量结构体进行copy(这里的assign是指一种策略,copy,strong,retain这种)操作,这个函数_Block_object_assign((void*)&dst->obj, (void*)src->obj,8)的第三个参数代表了是retain还是release。

用__block 修饰对象类型auto变量,同样会把捕获的对象包装成一个结构体,并且连同这个对象的修饰符一起捕获,这个结构体里会有copy和dispose两个函数用来管理对象的释放以及retain,copy等操作。

把栈上的block拷贝到堆上,__Block_byref_obj_1变量结构体也会拷贝到堆上,调用__Block_byref_obj_1变量结构体里的copy函数,copy函数里的assign函数会对外部捕获进来的__block 变量强引用或者弱引用(MRC下在这个结构体里,对捕获的变量是不会reatain,所以MRC不会出现循环引用问题)

在函数里写的block默认是栈上的,并不会对里面的对象强引用,但是ARC环境下,会对block默认进行copy操作,把block从栈上拷贝到堆上,同时也会将__main_block_impl_0结构体里所有的__Block_byref_obj_1变量结构体拷贝到堆上,堆里的__main_block_impl_0结构体对堆里的__Block_byref_obj_1结构体变量是强引用。当第二次或第三次再对着干block拷贝到堆上的就不会再堆这个__block修饰的变量拷贝,因为堆里已经有这个变量,其他拷贝的block只会指向着干__block变量(强引用)。

销毁过程

当block从堆上移除的时候,会调用dispose函数,release掉__block变量,以及捕获的变量,变量在被release的时候,如果是__Block_byref_结构体,会调用这个结构体的dispose函数。block结构体有个__forwarding指针是指向自己的,一旦被拷贝到堆上就会指向堆上的__Block_byref_结构体。这里就解决了内存在不同区的问题。

如果__block修饰的是对象类型的变量,还会在__main_block_desc_0里生成两个函数,这两个函数,copy在block由栈空间搬到堆空间时被调用,disoise在block被销毁时调用。

补充1:__main_block_impl_0里对__Block_byref_age_0结构体成员在ARC环境永远下是强引用,在__Block_byref_age_0结构体里,外面捕获进来的变量如果是对象才会有强弱引用之分,在MRC情况下是弱引用,MRC编译环境的情况下,在解决循环引用的问题上,可以用__block修饰符,也能解决循环引用的问题。

3:循环引用

Person *person =[ [Person alloc]init];

person.block = ^{NSLog(@"%@",person) };这两句代码,person里有个block变量,我们给这个block赋值,在block里打印person,这样person的block便捕获了自己,block强引用person,而block是person的属性,person也强引用着block,具体如图所示,block和person互相强引用,当person指针销毁时,person指针对person对象的强引用消失,但是block和person相互强引用,这两块内存永远被占着,造成内存泄露,引用关系如 图-3 所示。

图-03

还有种情况,比如在person的类里面,有个方法

-(void)method{ self.block = ^{NSLog(@"%@",_age) ;} }

里面没有显式的使用self,但是也会造成循环引用,OC方法里都有两个隐匿参数上面的方法代码实际是下面这个样子的,也会造成block捕获self造成强引用

- (void)method:(id)self cmd:(SEL)cmd{self.block = ^{NSLog(@"%@",_age) ;} }

__weak 和__unsafe_unretained区别:

__weak:不会产生c强引用,指向的对象销毁时,会自动让指针置为nil

__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址不变。

总结

一.什么是block

block是把代码块封装成一个函数,以及其上环境上下文(捕获的变量)捕获的一个OC对象。同样拥有类。

二.为什么产生循环引用

一个对象持有block,block又捕获了这个对象或者这个对象的属性

循环引用引用解决办法

1.使用__weak弱指针,断开循环引用

2.当__block 修饰的变量产生循环引用时,可以在可以在block代码块里把引用的对象赋值nil。但是这种情况有个不足之处,就是block不调用,循环引用的环就一直在。

三.block捕获对象的特性

1.对于局部基础数据auto类型对象是值捕获

2.对于局部静态变量是指针捕获

3.对于对象类型是强引用或者说是连同上修饰符一起捕获

4.对于全局变量则是不捕获,可以直接使用

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

推荐阅读更多精彩内容