IOS中Block的外部变量捕获机制

        本文作者主要参考Kazuki Sakamoto,Tomohiko Furumoto著,黎华译的《Objective-C高级编程:iOS与OS X多线程和内存管理》一书。在平时工作中遇到不少关于Block的相关困惑,于是在反复阅读了该书籍后,决定总结一篇关于Block的相关文章。如果还想深入了解Block,作者建议直接阅读上述书籍。由于作者水平有限,文章难免存在纰漏,恳求各位读者不吝赐教。

        Block变量捕获只针对Block中使用的变量,没有使用的不会追加到__xxx(外部函数名)_block_impl_x(序号,从0开始)C语言结构体中,也就是说不会被Block所捕获。


一、值变量类型的捕获

1、全局变量

        不会捕获,因为在Block变换的C语言函数中可以直接访问到,并且没有任何变化,可直接使用。

2、全局静态变量

        不会捕获,因为在Block变换的C语言函数中可以直接访问到,并且没有任何变化,可直接使用。

3、局部静态变量

        捕获,因为变量在Block变换的C语言函数作用域外部,会生成指针指向该变量,并保存在Block转换的C语言结构体中。

4、普通局部变量

        局部静态变量的捕获机制似乎也适用于局部变量的捕获,但是官方为什么没有这么做?

        这里说一下自己的理解,1)因为普通的值类型的局部变量的初衷只是在局部的作用域使用,超出作用域就不想再被其他地方所访问,因此也没有必要对其进行捕获。2)进行捕获的目的是,在Block内部进行对其操作时,外部的变量也能得到相同修改。对于对象类型的普通局部变量而言,例如一个Array对象,在对其添加一个Object后,显然外部Array也是会得到新增的,因为本质上它们指向同一片内存区域。而对于值类型的普通局部变量,如int类型,我们在Block内想修改它,唯一的手段就是修改它的值,但是这是不被允许的,需要加上__block修饰符,但加上__block修饰符之后,它就变成了__block结构体了,不再是普通的值类型了。


二、指针变量类型(对象)的捕获

        在Block结构体内部会生成相应的 指针变量(对象),如引用外部的array对象,在Block内部则生成 id __strong array的成员变量,这个成员变量的内存管理通过调用Block内部的__xxx_block_copy_x函数来持有外部的对象,通过__xxx_block_dispose_x来释放对象。

        所以,Block中使用附有__strong修饰符(默认生成的对象属于strong类型)的局部变量的对象和复制到堆上的__block变量由于被Block所持有,因而会超出其变量作用域而存在。

        注意:如果外界的对象被__weak修饰符修饰,那在Block结构体内部会生成相应的weak类型的对象,当外界的对象被释放时,由于weak指针自动置nil的原因,Block结构体内部的对象被置nil,虽然对其可以进行任何的方法调用,不会导致程序崩溃,但都是没有结果的。


三、__block对值变量的使用

        生成__block结构体,__block结构体内部生成一个值类型成员变量,其值等于外界值变量的值。此时在Block内部进行修改时,会以如下形式进行:

__Block_byref_val_0 *val = __cself->val ;

(val->__forwarding->val) = 1;

        通过__block结构体的__forwarding指针访问自己(或者是堆上自己的拷贝)来改变__block结构体内部的值。

        __block成员变量的内存管理通过调用Block内部的__xxx_block_copy_x函数来持有对象,通过__xxx_block_dispose_x来释放对象。


四、__block对指针变量(对象)的使用

        同样会生成__block结构体,__block结构体内部生成一个对象类型成员变量,其引用外界的对象。

        __block成员变量的内存管理通过调用Block内部的__Block_byref_id_object_copy_x函数来持有对象,通过__Block_byref_id_object_dispose_x来释放对象。

        注意:如果外界的对象被__weak修饰符和__block修饰,那在__block结构体内部会生成相应的__weak类型的对象,当外界的对象被释放时,由于weak指针自动置nil的原因,__block结构体内部的对象被置nil,虽然对其可以进行任何的方法调用,不会导致程序崩溃,但都是没有结果的。


五、__block变量的__forwarding指针

__Block_byref_val_0 *val = __cself->val;

  (val->__forwarding->val) = 1;

        在Block内部,来获取持有的__block变量,并通过上述方式修改变量的值,但是__forwarding指针的存在是不是看似多余呢?

        __block变量内部的__forwarding指针可以实现无论__block变量配置在栈上还是堆上时都能正确地访问__block变量。原因如下:

1-1 __block的内存区域变化

        这里会出现一种情况

        __block int val = 0;

        void (^blk)(void) = [ ^{ ++val; } copy ];

        ++val;

        blk();

        NSLog(@“%d”,val);

        打印结果:2

        开始,__block变量和Block都在栈上面,当Block调用copy方法后,Block被复制到堆中,而其内部调用的__block变量也会从栈复制到堆并被Block持有,此时__block变量有栈上的变量和堆上的变量,栈上的__block变量会将成员变量__forwarding指针的值替换为复制到目标堆上的__block变量用结构提实例的地址。

        这里曾经存在误区,又学习了一遍。此时可同时访问栈上/堆上的__block变量,但最终访问的都是同一处__block变量,如果没有发生__block变量从栈到堆的拷贝,那么在Block内部/外部访问__block变量都是属于栈上的同一个__block变量,反之则是访问堆上的同一个__block变量

        具体原因如下:

1-2 __block变量的访问过程

        这里附上书中的一段原话:通过该功能,无论是在Block语法中、Block语法外使用__block变量,还是__block变量配置在栈上或堆上,都可以顺利地访问同一个__block变量。


六、block从栈复制到堆的时机

        在ARC有效时,大多数情况下编译器会恰当的判断,自动生成将Block从栈复制到堆上的代码,有如下情况:

1、调用Block的copy实例方法时

2、Block作为函数返回值时或函数的参数中传递Block时

3、将Block赋值给附有__strong修饰符id类型的类或Block类型的成员变量时

4、在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时

        不过有时候需要手动将Block从栈复制到堆上,此时我们使用“copy实例方法”。

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

推荐阅读更多精彩内容