iOS Block

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

Block编译后的C++代码

iOS Block_第1张图片
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:

iOS Block_第2张图片
__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

iOS Block_第3张图片
interesting example of block variable

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

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

iOS Block_第4张图片
截获自动变量 

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无法捕获从外部传入其作用域中的引用

iOS Block_第5张图片
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)已经非法,而可否赋值成功跟对已释放的引用的非法访问有关

你可能感兴趣的:(iOS Block)