iOS Block

iOS中的block类似比如js中的闭包,叫做“带自动变量的匿名函数”,而实际上它更多地体现的是函数式编程的概念,在开启了clang 的 “-rewrite-objc”选项之后,可以发现block其实是作为 C++对象来实现的

Block编译后的C++代码

block编译结果示例

上述示例代码第二行 声明并定义了block  即blk

第3行以blk自身(作为__block_impl对象)作为参数调用实际的执行函数 __main_block_func_0

对作用域中自动变量的处理

处理方法:如果block中有取用自动变量的话,会在__main_block_impl_0中添加相应的成员变量以存储这些变量值

而对于需要修改自动变量的场景,需要为自动变量添加__block说明符,比如 __block int val = 10,所有block变量对应的C++对象为__Block_byref_val_0:

__Block_byref_val_0

__forwarding会赋值为对象自身的地址&val(注意,如果此结构体实例还在栈上则为栈上的地址,若在堆中,则为_forwarding会被赋值为此结构体实例在堆中的地址,所以无论_block变量配置在栈上还是在堆上,都能够正确地访问到到该变量(通过_forwarding->val))

block所定义的作用域中如果 val 赋值10则__Block_byref_val_0.val 会被赋值为10,其他自动变量亦会以此种形式保存在__Block_byref_val_0中

Block及__block变量 超过变量作用域 可存在的原因是什么呢?

原因归根到底是: Blocks提供了将Block和__block变量从栈上复制到堆上的方法来应对

ARC有效时,大多数情况编译器会自动生成将Block从栈上复制到堆上的代码,复制到堆上的Block将_NSConcreteMallocBlock类对象写进Block变量的isa, 哪些情况需要手动将block从栈上复制到堆上呢,比如栈上生成的block并没有引用其生成作用域中的变量,没有调用copy等情况,需要手动调用block的copy方法以让编译器将block从栈上复制到堆上。

 若在block中使用了的_Block变量会在Block从栈中复制到堆上的同时配置到堆上,相应地,当Block被 释放时,相应的_block变量所在的结构体实例也被释放(just the reference count being decreased, you know, maybe someone else is using it)

下面我们再来看一个未使用Block变量的例子,let's rock the cocoa

interesting example of block variable

在作用域结束时, array 本来应该被废弃掉的,局部作用域中其强引用会失效,但输出结果却是: array count = 1  array count = 2  array count = 3

这段代码编译器转换过后的C++代码与之前的例子不太一样:

截获自动变量 

OC高级编程里面说,一般而言,C语言结构体不能含有添加了__strong修饰符的变量,因为编译器无法判断何时进行初始化及废弃操作,不方便内存管理。但凡事都有例外,在Block用结构体中,OC运行库能够准确把握Block从栈上复制到堆上及从堆上被废弃的时机,所以在Block用结构体中可以附有__strong或__weak修饰符。

但自动变量array在其所在作用域结束之后是会被释放的,使Block对象在从栈上复制到堆上时会调用 __main_block_copy_0将array复制到成员变量array中,而其复制的起点则为__main_block_impl_0(.., id __strong _array...) :array(_array){..}; 即在初始化Block对象的时候__main_block_impl_0就已经将自动变量array的引用传递到了Block对象中。 __main_block_copy_0使用_Block_object_assign将对象赋值给Block用结构体的成员变量array中并持有该变量,其相当于retain实例方法。__main_block_dispose_0函数使用_Block_object_dispose函数释放成员变量array中的对象,相当于调用release实例方法。其实在使用__block变量的时候已经使用了这两种方法,只不过__block对象对应的类型为BLOCK_FIELD_IS_BYREF(另一种类型为对象:BLOCK_FIELD_IS_OBJECT)。

插播下声明,栈上的block复制到堆上的时机: 1 调用Block的copy实例方法    2 Block作为函数返回值返回时  3 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时   4 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时

所以,在上述源代码中不调用 copy方法时,会发生什么情况呢?答案是执行后,程序会强制结束,因为只有调用 _Block_copy函数才能持有截获的附有__strong修饰符的对象类型的自动变量值,不调用_Block_copy的情况下,自动变量会随作用域结束而被废弃。

附有 __strong修饰符的id或对象类型自动变量(同样适用于__strong修饰符修饰的__block id或对象变量)在Block函数从栈上复制到堆上时,会使用_Block_object_assign将自动变量对象赋值给Block用结构体中的成员变量,并持有此对象,并在Block销毁时由_Block_object_dispose释放

上例已经讲述了在Block中使用附有 __strong修饰符的自动变量的情况(同样适用于__block说明符与__strong修饰符同时使用的情况),对于附有__weak修饰符的自动变量的情况(同样适用于__block说明符与__weak修饰符同时使用的情况),由于自动变量在作用域结束时,__weak变量会被赋值nil,所以会有一些奇特的现象。__block与__unsafe_unretained的组合与后者表现相似(__unsafe_unretained 只是对象的指针,但不作引用计数,有野指针的危险),而__block与__autoreleasing的组合会引发编译错误。

Block循环引用的问题可能发生在将self直接或间接(Block函数体中引用了成员变量)地引用传进Block函数体中的情况。一般的处理循环引用的方法是使用__weak变量(或者在可以确保的情况下使用__unsafe_unretained变量),也可以通过__block变量 在Block创建之初持有 self,但运行过程中将__block变量置nil来避免循环引用。

ARC无效时的,手动调用copy/release

ARC无效时,一般需要手动将Block从栈复制到堆:

void (^blk_on_heap)(void) = [blk_on_stack copy];

[blk_on_heap release];

[blk_on_heap retain];  //持有

而对栈上的block持有是不起作用的   [blk_on_stack retain];

c语言中的copy/release为 Block_copy和Block_release: void (^blk_on_heap)(void) = Block_copy(blk_on_stack);          Block_release(blk_on_heap);

ARC无效时,__block说明符用来避免循环引用:因为当Block从栈复制到堆上时,若Block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain,反之则会被retain

Block无法捕获从外部传入其作用域中的引用

Block获取作用域中各种变量的引用 

上述示例代码中标注的4行代码的执行结果是怎样呢( cppstruct.result == 2, num == 3),现在揭晓答案

#1 res != 2  #2  n !=3  #3 d != 3

#4 ss == "this is a string"

可以基本得出的结论是Block在method:num:作用域释放时,其会持有cppstruct,num,cd的引用(即其地址),而会持有s 的值的拷贝。

res,n 及 d 在赋值后的值有时候并不等于其所赋引用中持有的值,推断的话应该是各引用(cppstuct,num,cd)已经非法,而可否赋值成功跟对已释放的引用的非法访问有关

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

推荐阅读更多精彩内容