__block变量存储域
Posted Haley_Wong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了__block变量存储域相关的知识,希望对你有一定的参考价值。
将Block赋值给strong 类型的block变量,或者赋值给strong 修饰的对象类型中的block成员变量时,block会从栈复制到堆上。那__block变量会有什么变化呢?
其实,当block 被从栈复制到堆上时,block中使用到的变量也会从栈赋值到堆上。
当block被从栈复制到堆前后,__block变量存储域变化如下:
- 1.block在栈上时,__block的存储域是栈,__block变量被栈上的block持有。
- 2.block被复制到堆上时,__block变量的存储域是堆,__block变量被堆上的block持有。
如果多个Block使用同一个__block变量时,一开始多个Block都会配置在栈上,所以__block变量也会配置在栈上。在任何一个Block从栈复制到堆上时,__block变量也会一并从栈复制到堆并被堆上的Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数。
而如果配置在堆上的Block被废弃,那么它所使用的__block变量也会被释放。
从这里可以看出__block变量的管理,与Objective-C的引用计数内存管理方式很相似。使用__block变量的Block,持有__block变量。如果Block被废弃,它所持有的__block变量也会被释放。
这里回顾一下,__block变量内部的__forwarding成员变量作用?
因为__block变量从栈复制到堆,所以在某一个时刻会有两个__block变量,如果将栈上的__forwarding指向堆上的__block变量,就可以保证不管你访问的是栈上的__block变量,还是堆上的__block变量,实际上访问的都是堆上的__block变量。
这样,写法是一致的,而且不管栈上的__block变量是否被释放,都能通过变量的指针地址正确的访问到堆上的__block变量。这样的设计真是非常完美。
当block 中使用__block变量时,__main_block_desc_0
结构体内部会多出两个成员变量copy
和 dispose
,这两个成员变量是函数指针类型,里面分别存储着两个静态函数__main_block_copy_0
和 __main_block_dispose_0
。
enum
/* See function implementation for a more complete description of these fields and combinations */
// 是一个对象
BLOCK_FIELD_IS_OBJECT = 3, /* id, NSObject, attribute((NSObject)), block, … */
// 是一个block
BLOCK_FIELD_IS_BLOCK = 7, /* a block variable */
// 被__block修饰的变量
BLOCK_FIELD_IS_BYREF = 8, /* the on stack structure holding the __block variable */
// 被__weak修饰的变量,只能被辅助copy函数使用
BLOCK_FIELD_IS_WEAK = 16, /* declared __weak, only used in byref copy helpers */
// block辅助函数调用(告诉内部实现不要进行retain或者copy)
BLOCK_BYREF_CALLER = 128 /* called from __block (byref) copy/dispose support routines. */
;
__main_block_copy_0
函数是将block内使用的__block变量,赋值给目标Block用结构体成员变量中,如果是对象类型变量,则持有它。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
// 这里如果使用多个__block变量,则会在该函数中依次调用_Block_object_assign
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->name, (void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign
函数当复制的是对象类型变量时,相当于retain实例方法,将对象类型变量赋值在__main_byref_name_1这样的结构体变量的成员变量中。
__main_block_dispose_0
函数内部使用_Block_object_dispose
函数,来释放赋值在Block结构体中的成员变量。
static void __main_block_dispose_0(struct __main_block_impl_0*src)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose
函数中如果传的是对象,则相当于release实例方法的函数,如果是非对象,猜测就是普通的释放变量了。
在Clang 转换出来的C++代码中没有看到调用__main_block_copy_0
和 __main_block_dispose_0
的地方,据说是在运行时将Block从栈复制到堆时以及堆上的Block被废弃时会调用这些函数。(这个目前还无法确认)
调用copy函数和dispose函数的时机
函数 | 调用时机 |
---|---|
copy函数 | 栈上的Block复制到堆时 |
dispose函数 | 堆上的Block被释放时 |
在哪些场景下栈上的Block会复制到堆上呢?
- 1.当调用Block的copy实例方法时
- 2.当Block作为函数返回值返回时
- 3.将Block赋值给附有__strong修饰符的block变量时
- 4.将Block赋值给附有__strong修饰符的对象中的block成员变量时
- 5.在方法名中含有usingBlock的Cocoa框架中的方法中
- 6.在GCD的API中传递Block时
在这些场景下,block就会被从栈上复制到堆上。
为什么在对象中添加block类型的property时,要使用copy修饰符?
其实在ARC下,使用strong
,retain
和使用 copy
是一样。
我们都知道,在ARC下,使用strong
等价于使用retain
,而在ARC下对Block执行retain,会被转化为objc_blockRetain
。
该函数在runtime中是这样实现的:
//
// The -fobjc-arc flag causes the compiler to issue calls to objc_retain/release/autorelease/retain_block
//
id objc_retainBlock(id x)
return (id)_Block_copy(x);
综上所述,在ARC下,对block使用strong
等价于使用retain
,而对block使用retain
,会在运行时调用_Block_copy
,所以这跟使用copy
是一样的效果。
而将block从栈复制到堆上时,可以防止block
或者block
中使用的变量被释放。
对block使用copy函数,不管是栈上的、堆上的、全局的block都不会出现问题。所以,我们就统一使用copy
来修饰了。
以上是关于__block变量存储域的主要内容,如果未能解决你的问题,请参考以下文章