__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结构体内部会多出两个成员变量copydispose,这两个成员变量是函数指针类型,里面分别存储着两个静态函数__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变量存储域的主要内容,如果未能解决你的问题,请参考以下文章

__block变量存储域

Block存储域探析

Block实现-block存储域

__block 说明符的作用以及其对Clang编译器的影响

为什么Block超出变量作用域还可以继续存在?

Block对象变量捕获(三)