Block底层原理

1、 block 定义

:带有自动变量的匿名函数。

匿名函数:没有函数名的函数,一对{}包裹的内容是匿名函数的作用域。

自动变量:栈上声明一个变量既不是静态变量,又不是全局变量,是不可以在栈内声明的匿名函数中使用的。但是在block中却可以,虽然使用block不用声明类,但是block提供了类似OC的类一样,可以通过成员变量来保护作用域外变量值的方法,那些再block的一对{}里面使用到,但却是在{}作用域外声明的变量,就是block捕捉的自动变量。

    block 表达式语句:^ 返回值类型(参数列表){表达式}

无参数无返回值block


    有参无返回值


有参数有返回值


无参数有返回值


用typedef定义block



2、block的分类

    block主要分为三类:

        全局block:_NSConcreteGlobalBlock;存储在全局内存中,相当于单例。

        栈block:_NSConcreteStackBlock;存储在栈内存中,超出其作用域则马上被销毁 。

        堆block:_NSConcreteMallocBlock;存储在堆内存中,是一个带引用计数的对象,需要自行管理其内存。    

    如何区分block存储区域?

    block不访问外部变量(包括栈和堆中的变量),此时为全局block,arc和mrc都是如此。

    

block访问外部变量:

    MRC环境下:访问外部变量的block默认是存储在栈中的。

    ARC环境下:访问外部变量的block默认是存储在堆中的(实际是放在栈区,然后ARC情况下又自动拷贝到堆区),自动释放。

    

在ARC环境下我们怎么获取栈block呢?

此时我们通过__weak不进行强持有,block就还是栈区的block。


此时我们通过__weak不进行强持有,block就还是栈区的block

ARC环境下,访问外部变量的block为什么要自动从栈区拷贝到堆区呢?

因为:栈上的block,如果其所属的变量作用域结束,该block就会被废弃,如同一般的自动变量。当然,block中的__block变量也同时会被废弃。

为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当的进行判断是否有必要将block从栈复制到堆,如果有,自动生成将block从栈复制到堆的代码。block的复制操作执行的是Copy实例方法。block只要调用了Copy方法,栈块就会变成堆块


    上面的代码中,函数返回的block是配置在栈上的,所以返回返回函数调用方法时,block变量作用域就被释放了,block也会被释放。但是,在ARC环境下是有效的,这种情况编译器会自动完成复制。

    在非ARC情况下则需要开发者调用Copy方法手动复制。

    将block从栈区复制到堆区非常消耗CPU,所以当block设置在栈上也能使用时,就不要复制了,因为此时的复制只是在浪费CPU资源。

block的复制操作,执行的是Copy实例方法。不同类型的block使用的Copy方法的效果如下:

   

根据表格我们知道,block在堆区Copy会造成引用计数增加,这与其它OC对象是一样的。虽然block在栈中也是以对象的身份存在,但是栈区没有引用计数,因为不需要,我们都知道栈区的内存由编译器自动分配释放。


底层分析

使用 clang将OC代码文件转换成C++文件,查看block的方法。

    

    首先切换到目标代码所在文件夹下,


输入下面的指令


注意:clang -x objective-c -rewrite-objc -isysroot  意思是编译绝对路径中的文件,

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk  是你的xcode中SDK的路径,


BlockStudyViewController.m 是你要编译的文件

执行完后在项目中会多一个.cpp文件


打开文件后我们可以看到ViewDidLoad 方法被编译的样子


定位到myBlock100, 我们发现myBlock100被构造成了__BlockStudyViewController__test03_block_impl_0


寻找 __BlockStudyViewController__test03_block_impl_0


可以看到__BlockStudyViewController__test03_block_impl_0是一个结构体。同时我们也可以说block是一个__BlockStudyViewController__test03_block_impl_0类型的对象,这也是为什么block能够%@打印的原因

block自动捕获外部变量

仍然是上面的例子,

block自动捕获的外部变量,在block的函数体内是不允许被修改的。


编译器生成了一个同名的变量,__BlockStudyViewController__test03_block_impl_0中的num是值拷贝,因此,在block内存会生成一个,内容一样的同名变量,此时如果在函数体内进行num++的操作,则编译器就不清楚该去修改哪个变量。所以block自动捕获的变量,在函数体内部是不允许修改的。


那么我们要修改外部变量要怎么办呢?

1、__block修饰外部变量。

2、将变量定义成全局变量

3、将变量用参数的形式,传入block里面

__block 原理

    

分析编译文件.cpp


在__BlockStudyViewController__test03_block_impl_0中,我们发现num变成了一个__Block_byref_num_0类型的对象,

在__BlockStudyViewController__test03_block_func_0中,由之前的值拷贝变成了指针拷贝。

总结:__block修饰外界变量的时候:

    外界变量会生成__Block_byref_num_0结构体;

     结构体用来保存原始变量的指针和值 ;

    将变量生成的结构体对象的指针地址传递给block,然后在block内部就可以对外界变量进行修改了。    

__Block_byref_num_0中的__forwarding 是什么?


可以看到,__forwarding是一个指向自己本身的指针(自身为结构体)。

那么,在Copy操作之后,既然__block变量也被Copy到堆区上了,那么访问该变量是访问栈上的还是堆上的呢?这个时候我们就要来看一下,在Copy过程中__forwarding的变化了:


可以看到,通过__forwarding,无论是在block中,还是block外访问__block变量,也不管该变量是在栈上或是在堆上,都能顺利的访问同一个__block变量。

注意:这里与上面的结论并不矛盾。大家要主要到局部变量a被__block修饰之后,会变成__Block_byref_a_0结构体对象。所以无论是在栈区还是在堆区,只要__forwarding指向的地址一样,那么就可以在block内部修改外界变量。这里大家要仔细观察一下__Block_byref_a_0结构体。


举例再次理解值捕获

block只能捕获局部的非静态变量的值,局部非静态对象得值,对于全局和静态变量或静态对象是不能捕获值的,

以局部static对象为例


block并未能捕获值,而是捕获地址

分析cpp文件


看到代码1处已经是二重指针,3处在调用的时候取出的是指向name变量地址的内存,而不是name所指向的内存,所以要想获取name所指向的内存,4处通过*name取值进行传参.

捕获总结

    如果是局部变量的话,如果不持有他的话是不是过了作用域就释放了,那就不能完成方法的正常调用,所以对于局部变量,一定会捕获的;

    对于全局变量,block捕获变量的原因要使用变量,既然是全局变量,那在哪都可以访问,所以不需要捕获;

    那为什么局部变量有的是传地址有的是传值呢,对于非static修饰的局部变量其实是auto的,这种变量是放在栈区的,过了作用域就会被系统回收,如果block捕获变量的地址的话,那可能捕获的地址已经被系统回收,或者已经被其他的对象占用了,这个时候程序会出现无法预料的异常,但是如果是static修饰的,是放在数据区的,不会随着作用域的而销毁,从而放地址是安全的。


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

推荐阅读更多精彩内容