Block探究

block的原理是怎样?本质是什么?

block本质上也是一个OC对象,block是封装了函数调用与及调用环境的OC对象,源码实例图:

源码实例图

另外block布局底层结构图如下


布局图


block的变量捕捉(capture)

为了保证block能够正常访问外部的变量,block有个变量捕捉机制

捕捉机制图

auto 变量捕捉

auto变量捕捉图1

另外auto变量的捕捉是值传递,这个和static和全局变量都是不一样的,可以通过clang的源码查看auto和static的区别:

auto static 测试图
源码展现图

可以看到static是指针传递,而auto是值传递,有这样的差异是因为在执行函数之后age就变成了垃圾数据,所以执行block的时候,不可能去访问age的内存,但是static 的内存是一直贯穿整个运行的生命周期的?

全局变量,static全局变量,staitc局部变量


static 全局 局部
源码图

可以看到全局变量并没有捕获到block里面,但是局部的static会,因为局部变量只能在局部中访问,并且是跨域访问,所以为了能访问正确,一定需要捕捉进去,因为全局变量直接在data区域,是可以直接访问的,不需要捕获进去,循环引用也是因为基于这种捕捉情况下产生的,例如

- (void)test{

    void (^block)(void) = ^{

        NSLog(@"-----%p",self);

    }

}

因为OC的机制,test方法会自动携带(id self,SEL _cmd)两个默认参数,所以可以当做是局部变量捕捉进去,当出现block引用self,self又引用block的时候就会导致循环引用了,上面的例子,目前这样看来当然是不会的,这个只是说明是导致循环引用的一个原理而已

对象类型的auto变量捕捉

当block内部访问了对象类型的auto变量时,如果block是在栈上,将不会auto变量产生强引用,如果block被拷贝到堆上,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong,__weak,__unsafe_unretained)作出相应的操作,形成强引用(retain)或者弱引用,如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)

调用时机图

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

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

对象类型的__block变量

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

_Block_object_dispose((void *)&src-p,8/*BLOCK_FIELD_IS_OBJECT*/);

对象的auto和__block

当__block被拷贝到堆上,会调用__block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong,__weak,__unsafe_unretained)作出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅仅限于ARC时会retain,MRC时不会retain),如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)

block的类型

block有3中类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承NSBlock类型

__NSGlobalBlock__(_NSConcreteGlobalBlock)

__NSStackBlock__(_NSConcreteStackBlock)

__NSMallocBlock__(_NSConcreteMallocBlock)

block的类型图1


block的类型图2

证明:

block style类型证明图

从图中我们可以看到这和上面的图片描述不一样,原因是什么呢?在解释这个问题之前,我们在看看这个例子:

stackblock图

从上面的图是不是可以悟出什么东西来呀,上面arc的情况下能正常打印结果,说明了arc帮我们额外做了一些操作,例如copy block从栈到堆,打印[block copy] 的值和block的地址值是一样就说明了这一点,另外从clang出来的全部都是satckblock类型的,说明llvm编译器在运行时帮我们做了一些转换类型的操作

mrc 模拟 arc

block中的copy

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

1.block作为函数返回值时

SLBlock myblock(){

    int age = 10;

    return ^{

        NSLog(@"------%d",age);

    }

}

终端打印结果图:

block作为返回值

注意:如果没有age1变量的话,因为没有访问auto变量,会当做全局变量处理,打印出来是global类型

2.将block赋值给__strong指针时

int main(int argc,const char* argv[]) {

    @autoreleasepool {

        int age =10;

        SLBlock block = ^{//默认是strong指针引用

            NSLog(@"---------%d", age);

        };

        NSLog(@"%@", [block class]);

 }

    NSLog(@"%@", [block class]);

}

如果将上述的代码更改成

int age = 10

NSLog(@"%@",[^{NSLog(@"----------%d",age);} class]);

这个时候这个打印应该是stack,因为没有strong指针引用


3.block作为Cocoa API中方法名含有usingBlock的方法参数时

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

MRC下block属性的建议写法

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

ARC下block属性的建议写法

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

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

__blcok 修饰符

__block 可以用于解决block内部无法修改auto变量值的问题,__block不能修饰全局变量,静态变量(static),编译器会将__block变量包装成一个对象

__block 声明变量图
auto 声明变量图

__block 的内存管理

当block在栈上,并不会对__block变量产生强引用,当block被copy到堆上时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对_block变量形成强引用(retain),如下图所示:

block copy 到堆中强引用图

当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose 函数会自动释放引用的_block变量(release),如下图所示:


block dispose图

从clang出来的源码中,我们可以发现里面__block修饰的会有一个__forwarding指针,这个指针是干嘛用的呢?然而这里__forwarding指针真的永远指向自己么?我们来做一个实验

MRC 

可以看到MRC环境下,var->forwarding->var(age的指针)是相同的,说明栈中的block并没有拷贝到堆中,并且此时的forwarding指向的就是自己栈上block的内存地址,再看一下这个情形:

MRC
ARC

可以看到MRC下block使用copy,和ARC下直接定义是一样的情况,说明ARC下,自动帮我们将strong 引用的block拷贝到了堆中,这个时候,可以发现两个age的地址值并不一样,由此可以说明_forwarding 指针指向的不是同一个并且由打印结果age的值可以看到,block里面age的值和block外age的值是一样,都是30,所以在使用__block之后,在栈中访问age的时候,val->forwarding->val 访问的也是堆中的age值,在堆中访问age的时候,val->forwarding->val 访问的是堆中的age值,那这个说明了forwarding是为了让我们更好的管理内存的,不论现在block是出于栈中还是堆中,都不会影响到寻找到的相关信息,当block是在栈中,__forwarding指向的就是栈本身的地址,当block copy到堆中的时候,__forwarding指针指向的就是堆本身的地址,如下图所示:

__forwarding指针

上面中有没有人存在一个这样的问题,就是age的地址值,到底是__Block_byref_age_0 *age 的地址值,还是__Block_byref_age_0 *age 里面的变量age的地址值呢?我们来证明一下:

实例图
证明图


__weak问题

在使用clang转换OC为C++代码时,可能会遇到以下问题

cannot create __weak reference in file using manual reference

解决方案:支持ARC,指定运行时系统版本,比如:

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

循环引用

例子1:

测试例子
打印结果

可以看到在打印之前,person因为出了作用域,应该被销毁才对的,但是这里斌没有执行dealloc操作,说明person没有被释放,内存泄漏了,我们来分析一下内存分布图:

例子1内存分布图

可以看到运行到大括号外面的时候,箭头1销毁,但是这个时候2,3还是相互引用,导致不能释放,所以出现了循环引用,这个时候应该怎么解决呢?直接让其中一根线是弱引用就可以了,3弱引用也可以,2弱引用也可以,因为这里block是person的一个属性,所以想person销毁的时候,block就销毁,那么我这里就是让block里面的引用弄成弱引用就可以了,例如下图所示:

弱引用

相应的代码更改成为:

代码改造图
打印结果图

大括号结束后,引用1销毁,person没有强引用指向,person销毁,person销毁之后,因为3是若引用,所以block销毁,内存循环应用解除,这里除了用__weak 之外,使用__unsafe__unretained也不会产生强引用,但是不安全,指向的对象销毁之后,指针存储的地址不变,再次访问的时候会导致野指针,__weak会赋值成nil,安全;当然通过__block 也可以,具体可以看下面的补充要点;

补充要点

1.block的属性修饰词为什么是copy?使用block有哪些使用注意?

block一旦没有进行copy操作,就不会在堆上,需要注意循环引用问题,例如下图展示:

循环引用图

使用ARC解决循环引用

arc 解决循环引用

使用MRC解决循环引用

MRC解决循环引用

MRC情况下,如果不加__unsafe_unretained ,或者__block 修饰的话,如果block使用了copy拷贝到堆上的时候,因为声明的变量是强引用,在内部会对变量使用retain一次,这样最后就会导致强引用了,也就是会循环引用

可以添加微信一起交流学习:fslskz

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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的使用可以说是经常遇到到的了吧,GCD,网络请求,动画都随处可见block的影子...
    飞奔的小鲨鱼阅读 276评论 0 0
  • 提示:下面会把OC相应的类转化为C++代码,OC代码转C++代码的生成 一、block 知识回顾block 是一个...
    IIronMan阅读 648评论 0 2
  • 一、Block的底层结构及本质 (1)block本质: 从代码可以看出,Block的本质就是NSObject. 也...
    王的for阅读 497评论 0 2
  • 参考篇:iOS-Block浅谈 前言:本文简述Block本质,如有错误请留言指正。 第一部分:Block本质 Q:...
    梦蕊dream阅读 61,060评论 41 322