找回密码
 立即注册
首页 业界区 业界 Block Copy 的内存布局详解

Block Copy 的内存布局详解

郁兰娜 3 小时前
Block的Copy操作,都会调用到_Block_copy函数。
在LLVM工程源码runtime.c文件下给出了相关定义:
  1. void *_Block_copy(const void *arg) {    return _Block_copy_internal(arg, WANTS_ONE);}
复制代码
_Block_copy内部调用_Block_copy_internal函数。
arg是要被拷贝的源Block。
WANTS_ONE只在GC环境下有用,ARC环境下可以忽略。
1 Copy 全局 Block

Copy全局Block最简单,直接返回全局Block本身,不做任何操作:
  1. static void *_Block_copy_internal(const void *arg, const int flags) {    struct Block_layout aBlock = (struct Block_layout *)arg;    ...    else if (aBlock->flags & BLOCK_IS_GLOBAL) {        // 全局 Block 直接返回        return aBlock;    }    ...}
复制代码
struct Block_layout定义在LLVM工程源码中的Block_private.h中:
  1. struct Block_layout {    void *isa;    int flags;    int reserved;     void (*invoke)(void *, ...);    struct Block_descriptor *descriptor;    /* Imported variables. */};
复制代码
本质上就是Block结构体不包含捕获变量的部分。
2 Copy 堆 Block

Copy堆Block也很简单,直接将Block自身的引用计数加1:
  1. static void *_Block_copy_internal(const void *arg, const int flags) {    struct Block_layout *aBlock = (struct Block_layout *)arg;    ...    if (aBlock->flags & BLOCK_NEEDS_FREE) {        // 对于堆 Block 直接增加 Block 本身的引用计数        latching_incr_int(&aBlock->flags);        return aBlock;    }    ...}
复制代码
增加Block的引用计数调用latching_incr_int函数:
  1. // runtime.cstatic int latching_incr_int(int *where) {    while (1) {        int old_value = *(volatile int *)where;        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {            // 如果引用计数达到最大值 BLOCK_REFCOUNT_MASK,直接返回            return BLOCK_REFCOUNT_MASK;        }        if (OSAtomicCompareAndSwapInt(old_value, old_value+1, (volatile int *)where)) {            // 引用计数值加 1            return old_value+1;        }    }}
复制代码
如果Block本身的引用计数到达了最大值BLOCK_REFCOUNT_MASK,那么直接返回这个最大值;否则,Block自身的引用计数加1。
但是经过测试,现实中的实现,并不总是遵循LLVM的源码。
Block的flags低15bit并不都是用来进行引用计数的。

flags中的最低位,也就是第0bit不参与ARC环境下的引用计数。
看LLVM源码推测它是作为GC环境下的一个标志位使用:
  1. static void *_Block_copy_internal(const void *arg, const int flags) {    struct Block_layout *aBlock = (struct Block_layout *)arg;    ...    else if (aBlock->flags & BLOCK_IS_GC) {        // GC 环境下判断了 flags 的最低位        if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 1)) {            // Tell collector to hang on this - it will bump the GC refcount version            _Block_setHasRefcount(aBlock, true);        }        return aBlock;    }    ...}
复制代码
由于flags的最低位没有参与到ARC环境下的引用计数,因此实际获取当前Block引用计数的掩码不是BLOCK_REFCOUNT_MASK:
  1. BLOCK_REFCOUNT_MASK =     (0xffff)
复制代码
而是0xfffe。
这种情况,相当于把Block的引用计数整体向左移动了1bit。
因此,如果给引用计数加1,应该是加2。
换句话说,要计算Block的引用计数,通过掩码0xfffe取出值后,要右移1bit,也就是除以2。
比如通过掩码取出的值是4,实际的引用计数应该是2。
下面是对应的汇编码:
  1.     // 使用掩码 0xfffe 获取 flags 中的值->  0x18013f93c : mov    w8, #0xfffe               ; =65534     0x18013f940 : ldr    w9, [x20, #0x8]    0x18013f944 : bics   wzr, w8, w9    0x18013f948 : b.eq   0x18013f964               ;     // 将引用计数加 1,就是加 2    0x18013f94c : add    w10, w9, #0x2
复制代码
3 Copy 栈Block

Copy栈Block稍微复杂一点。
第1步,需要在堆上创建一个和栈Block同样大小的堆Block。
第2步,将栈Block结构体里的值,原封不动的拷贝到新创建的堆Block。
第3步,设置堆Block flags中的BLOCK_NEEDS_FREE标志位。
第4步,设置堆Block的引用计数为1。
第5步,看当前堆Block有没有copy_helper函数,有的话就执行。
第6步,设置堆Block的isa指针为MallocBlock。
相关的LLVM工程源码在如下:
  1. static void *_Block_copy_internal(const void *arg, const int flags) {    struct Block_layout *aBlock = (struct Block_layout *)arg;    ...    // Its a stack block.  Make a copy.    if (!isGC) {        // 1. 创建同样大小的堆 Block        struct Block_layout *result = malloc(aBlock->descriptor->size);        if (!result) return (void *)0;        // 2. 原封不动拷贝栈 Block 的值到堆 Block        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first        // reset refcount        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed        // 3. 设置 flags 中的 BLOCK_NEEDS_FREE 标志        // 4. 设置堆 Block 的引用计数为 1        result->flags |= BLOCK_NEEDS_FREE | 1;        // 5. 设置堆 Block 的 isa 指针为 MallocBlock        result->isa = _NSConcreteMallocBlock;        // 6. 根据情况,看是否需要调用堆 Block 的 copy_helper        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);            (*aBlock->descriptor->copy)(result, aBlock); // do fixup        }        return result;    }    ...}
复制代码
从上面代码看只有第6会有不同的执行路径。
根据前面我们对Block结构体的分析,Block要有copy_helper,常见的情形为:

  • 捕获了OC对象
  • 捕获了Block对象
  • 捕获了需要进行拷贝操作的C++对象
  • 捕获了需要进行拷贝操作的Struct结构体
  • 捕获了 __block变量
下面就看下无copy_helper和有copy_helper的情形。
3.1 无 copy_helper

假设有下面的OC代码:
  1. void blockTest() {  int bi = 5;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + bi;  };}
复制代码
上面代码中的Block捕获了一个整型变量bi,因此不会有copy_helper。
进行拷贝之后,内存布局图为:

3.2 copy_helper 拷贝 OC 对象

假设有下面的OC代码:
  1. void blockTest() {  X *x = [X new];  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + x.i;  };}
复制代码
上面代码中的Block捕获了一个OC对象x。
由于拷贝操作的前5步将栈Block内容原封不动复制到了堆Block,这样导致对对象x的引用增加了,但是对象x的引用计数确没有增加:

这种情况下的copy_helper先清除堆Block里对对象x的引用,然后调用objc_storeStrong函数。
objc_storeStrong函数内部增加对象x的引用计数,然后将对象x的地址赋值给堆Block。

对应的伪代码如下:
  1. // src 是栈 Block// dest 是堆 Blockcopy_helper(id dest, id src) {  dest.x = nil;  objc_storeStrong(&dest.x, src.x);}
复制代码
注意,上面代码拷贝的时候,仅仅是增加了对象x的引用计数,而没有对对象x也进行拷贝。
换句话说,Block执行的是浅拷贝。
3.3 copy_helper 拷贝 Weak OC 对象

假设有下面的OC代码:
  1. void blockTest() {  X *x = [X new];  __weak X *wx = x;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + wx.i;  };}
复制代码
上面代码Block捕获了一个弱引用的OC对象wx。
和拷贝Strong类型的OC对象一样,拷贝操作的前5步将栈Block内容原封不动的复制到了堆Block里。
这样导致堆Block新增了一个对对象wx的弱引用,但是这个弱引用还不在系统的弱引用表中。
因此,这种情况下的copy_helper会调用objc_copyWeak函数。
objc_copyWeak函数将堆Block的弱引用指向对象wx,同时将这个弱引用记录到系统的弱引用表中。

对应的伪代码如下:
  1. // src 是栈 Block// dest 是堆 Blockcopy_helper(id dest, id src) {  objc_copyWeak(&dest.wx, &src.wx);}
复制代码
3.4 copy_helper 拷贝 Block 对象

假如有下面的OC代码:
  1. void blockTest() {  int i = 5;  void(^b)(void) = ^{    NSLog(@"%d", i);  };  void(^blk)(int, int, int) = ^(int i, int j, int k) {    b();  };}
复制代码
上面代码中Block blk捕获了另一个Block b。
由于拷贝操作的前5步将栈Block内容原封不动复制到了堆Block,这样导致对Block b的引用增加了,但是对Block b的引用计数确没有增加。

在这种情况下,copy_helper会调用_Block_copy函数拷贝Block b。
在ARC环境下,Block b已经存在堆上了。
根据前面所述,_Block_copy只会增加Block b的引用计数。
最后,让堆Block指向Block b。

整个拷贝流程和拷贝Strong OC对象类似。
区别是捕获的OC对象调用objc_storeStrong处理,而捕获的Block调用_Block_copy来处理。
对应的伪代码如下:
  1. // src 栈 Block// dest 堆 Blockcopy_helper(id dest, id src) {  _Block_object_assign(&dest.b, src.b, 7);}_Block_object_assign(id *dest, id src, int flags) {  if (flags & BLOCK_FIELD_IS_BLOCK == BLOCK_FIELD_IS_BLOCK) {    *dest = _Block_copy(src);  }}
复制代码
_Block_object_assign函数的第三个参数7是一个标识,表示当前要拷贝什么数据类型。
完整的定义在LLVM工程源码的CGBlocks.h中:
  1. enum BlockFieldFlag_t {  BLOCK_FIELD_IS_OBJECT   = 0x03,  /* id, NSObject, __attribute__((NSObject)),                                    block, ... */  BLOCK_FIELD_IS_BLOCK    = 0x07,  /* a block variable */  BLOCK_FIELD_IS_BYREF    = 0x08,  /* the on stack structure holding the __block                                    variable */  BLOCK_FIELD_IS_WEAK     = 0x10,  /* declared __weak, only used in byref copy                                    helpers */  BLOCK_FIELD_IS_ARC      = 0x40,  /* field has ARC-specific semantics */  BLOCK_BYREF_CALLER      = 128,   /* called from __block (byref) copy/dispose                                      support routines */  BLOCK_BYREF_CURRENT_MAX = 256};
复制代码
枚举值的作用看注释基本就能明白。
3.5 copy_helper 拷贝 Weak Block 对象

假如有如下代码:
  1. void blockTest() {  int i = 5;  void(^b)(void) = ^{    NSLog(@"%d", i);  };  __weak void(^wb)(void) = b;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    wb();  };}
复制代码
上面代码中Block捕获了一个弱引用的Block wb。
这种情形和copy_helper拷贝弱引用的OC对象类似。
copy_helper会调用objc_copyWeak函数。

对应的伪代码为:
  1. // src 栈 Block// dest 堆 Blockcopy_helper(id dest, id src) {  objc_copyWeak(&dest.wb, &dest.src);}
复制代码
3.6 copy_helper 拷贝 C++  值对象

假如有下面的代码:
  1. // C++ 类class Y {  public:  X *x; // OC 类  int i;};void blockTest() {  Y y;  y.x = [X new];  y.i = 5;  void(^blk)(int, int, int) = ^(int i, int j ,int k) {    int result = i + j + k + y.i;  };}
复制代码
上面代码中定义了一个C++类Y,它里面有一个OC成员变量x。
代码中的Block捕获了这个C++值对象,也就是这个C++对象直接生成在栈里面,而不是堆上。
Block捕获这种C++值对象,等价于捕获了这个对象里面的每一个成员变量,内存布局如下:

从内存布局图上可以看到,这等价于Block捕获了一个OC变量x和一个整型变量i。
虽然效果是等价的,但是copy_helper的实现上还是有区别的。
copy_heoper内部会调用C++类Y的默认拷贝构造函数。
由于我们没有提供,编译器自动为类Y合成了一个,伪代码如下:
  1. Y::Y(const Y *other) {  // 引用计数 +1  this->x = objc_retain(other->x);  this->i = other->i;}
复制代码
默认拷贝构造函数将C++对象中的OC成员变量x引用计数加1。

copy_helper的伪代码如下:
  1. // src 是栈 Block// dest 是堆 Blockcopy_helper(id dest, id src) {  dest.y = Y::Y(&src.y);}
复制代码
那如果C++对象内部引用的是一个Weak类型的OC对象呢?
比如有如下代码:
  1. class Y {  __weak X *wx; // OC 对象  int i;};
复制代码
这种情形拷贝过程几乎一模一样。
唯一的区别是copy_helper函数调用的C++拷贝构造函数会使用objc_copyWeak函数,对应的伪代码如下:
  1. // src 是栈 Block// dest 是堆 Blockcopy_helper(id dest, id src) {  dest.y = Y::Y(&dest.y, &src.y);}Y::Y(const Y *other) {  objc_copyWeak(&this->x, &other->x);  this->i = other->i;}
复制代码
对应的内存布局如下:

虽然整个流程看起来比价复杂,但是本质上还是和copy_helper拷贝Strong类型或者Weak类型的OC对象效果一致。
那C++对象本身可以声明成Weak吗?比如:
  1. // 不能这样声明__weak Y y;
复制代码
答案是不可以,因为__weak是OC才有的语义,C++下不支持。
另外,C++环境下的结构体struct和class本质上是一样的。
也就是说,下面代码定义完全一样:
  1. class Y {  public:  X *x; // OC 对象  int i;};struct Y {  public:  X *x; // OC 对象  int i;};
复制代码
3.7 copy_helper 拷贝 C++ 对象指针

第3.6节Block捕获的是C++值对象,下面来看一下捕获C++对象指针的情形。
假如有如下的代码:
  1. class Y {  public:  X *x; // OC 对象  int i;};void blockTest() {  // C++  对象指针  Y *y = new Y;  y->x = [BlockK x];  y->i = i;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + y->i;  };}
复制代码
上面代码定义了一个C++对象指针Y *。
Block内部捕获了一个C++对象指针变量y。
Block的内存布局如下:

从上图可以看到,这种情况下,Block没有copy_helper。
因此执行的是无copy_helper的拷贝,拷贝后的内存布局如下:

3.8 copy_helper 拷贝 C 结构体

正如3.6节所说,C++环境下的sturct和class等价,这里说的结构体专指C环境下的结构体。
假如有如下代码:
  1. typedef struct {  X *x; // OC 对象  int i;} Y;void blockTest() {  Y y;  y.x = [X new];  y.i = 5;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + y.i;  };}
复制代码
上面代码定义了一个结构体Y,它里面有一个OC成员变量x。
Block内部捕获了一个结构体变量y。
单从Block的内存布局上看,它和捕获一个C++值对象一模一样:

但是由于C结构体没有拷贝构造函数,在copy_helper执行拷贝的时候,内部调用的是编译期合成的copy_construct函数。
copy_construct函数的伪代码如下:
  1. void copy_construct(Y *dest, Y *src) {  // x 的引用计数加 1  dest->x = objc_retain(src->x);  dest->i = src->i;}
复制代码
copy_construct内部会将结构体内的OC成员变量的引用计数加1:

copy_helper的伪代码如下:
  1. // src 是栈 Block// dest 是堆 Blockcopy_helper(id dest, id src) {  construct_copy(&dest.y, &src.y);}
复制代码
那如果C结构体引用一个Weak类型的OC变量呢?
比如有如下代码:
  1. typedef struct  {  __weak X *x; // OC 对象  int i;} Y;
复制代码
这种情形拷贝过程几乎一模一样。
唯一的区别是copy_helper函数调用的copy_construct会使用objc_copyWeak函数,对应的伪代码如下:
  1. // src 是栈 Block// dest 是堆 Blockcopy_helper(id dest, id src) {  copy_construct(&dest.y, &src.y);}void copy_construct(Y *dest, Y *src) {  objc_copyWeak(&dest->x, &src->x);;  dest->i = src->i;}
复制代码
对应的内存布局如下:

基于和C++对象同样的原因,C结构体本身也不能声明为__weak:
  1. // 不能这样声明__weak Y y;
复制代码
3.9 copy_helper 拷贝 C 结构体指针

第3.8节Block捕获的是C结构体自身,下面来看Block捕获C结构体指针的情形。
假如有如下的代码:
  1. typdef struct {  X *x; // OC 对象  int i;} Y;void blockTest() {  // C 结构体指针  Y *y = malloc(sizeof(Y));  y->x = [X new];  y->i = i;    void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + y->i;  };}
复制代码
上面代码定义了一个C结构体指针Y *。
Block捕获了这个结构体指针变量y。
Block的内存布局如下:

从图上可以看到,这种情形下,Block不会有copy_helper。
因此执行的的是无copy_helper拷贝,拷贝后的内存布局如下:

4 copy_helper 拷贝 __block 变量

终于到了拷贝__block变量的环节了,呜~~~☕️☕️
之所以放到最后,是因为拷贝__block变量和上面的过程很类似,值得单开一节。
4.1 Copy 全局 __block 变量

由于__block只能修饰局部变量,不会有这种情况出现。
4.2 Copy 堆 __block 变量

Copy堆__block变量很简单,直接将__block变量的结构体引用计数加1。
同时,__block变量的flags的低15bit,它的最低位也没有参与引用计数。
这就和拷贝堆Block一样,如果flags中的数是4,那么实际的引用计数是2。
如果引用计数达到了最大值0xfffe,那么就直接返回,什么也不做。
对应的伪代码为:
  1. // src  Block// dest  Block// byref 是 Block_byref 结构体copy_helper(id dest, id src) {  _Block_object_assign(&dest.byref, src.byref, 8);}_Block_object_assign(id *dest, id src, int flags) {  // 当前要处理 Block_byref  if (flags & BLOCK_FIELD_IS_BYREF == BLOCK_FIELD_IS_BYREF) {    // src Block_byref 已经在堆上    if (src.flags & BLOCK_NEEDS_FREE) {      *dest = src;      if (src.flags & 0xfffe == 0xfffe) {        // 到达最大引用计数什么也不做        return;      } else {        // 未达到最大引用,计数加 1        *dest.flags += 2;      }    」  }}
复制代码
上面代码中传递给_Block_object_assign的第三个参数是8,代表BLOCK_FIELD_IS_BYREF,表示当前要进行操作的是Block_byref结构体。
后面可以看到,对Block_byref结构体的操作,都在_Block_object_assign函数中进行。
4.3 拷贝栈 __block 变量

_Block_object_assign拷贝栈__block变量稍微复杂一点。
第1步,需要在堆上创建一个和栈Block_byref大小一样的堆Block_byref。
第2步,将堆Block_byref的isa设置为0。
第3步,设置堆Block_byref flags中的BLOCK_NEEDS_FREE标志位,表明已经在堆上了。
第4步,设置堆Block_byref 的引用计数为2。
为什么不是设置为1呢?后面就可以看到原因。
第5步,将堆Block_byref的fowarding指针指向堆Block_byref自身。
第6步,将栈Block_byref的fowarding指向堆Block_byref。
这就是为什么第3步要设置堆Block_byref的引用计数为2的原因。
因为除了堆Block指向堆Block_byref之外,栈Block_byref的forwarding指针也指向了堆Block_byref。
第7步,判断栈Block_byref有没有byref_keep函数。
如果没有,那么直接将栈Block_byref剩余部分原封不动的复制到堆Block_byref对应的位置。
如果栈Block_byref有byref_keep函数,那么将byref_keep与byref_destroy复制到堆Block_byref。
接下来,要判断栈Block_byref的flags中,有没有设置BLOCK_BYREF_LAYOUT_EXTEND标志位,也就是有没有捕获结构体或者C++对象。
如果BLOCK_BYREF_LAYOUT_EXTEND设置了,将栈Block_byref中的variable_layout复制到堆variable_layout。
最后,调用byref_keep函数。
根据之前对Block_byref结构体的分析,Block_byref要有bref_keep的情形为:

  • __block 变量为OC对象
  • __block 变量为Block对象
  • __block 变量为需要进行拷贝操作的C++对象
  • __block 变量为需要进行拷贝操作的Struct结构体
而byref_keep的作用,和Block的copy_helper一模一样。
接下来,就来看看各种情形。
4.3.1 无 bref_keep

假如有下面的代码:
  1. void blockTest() {  __block int byref_i =5;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + byref_i;  };}
复制代码
上面代码生命了一个int类型的__block变量,因此不会有bref_keep。
拷贝完成之后的内存布局为:

从上面的内存图可以看到,栈Block_byref的forwarding指针也指向了堆Block_byref。
还记得__block变量都是通过forwarding指针访问吗?
这样一来,无论是栈Block_byref还是堆Block_byref都是访问堆Block_byref。
4.3.2 byref_keep 拷贝 OC 对象

假如有如下代码:
  1. void blockTest() {  __block X *byref_x = [X new];  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + byref_x;  };}
复制代码
上面代码定义了一个Strong类型的__block变量byref_x。
经过拷贝之后的内存布局为:

从内存图上可以看到,拷贝完成之后,栈Block_byref已经不指向byref_x对象了。
所以,byref_x的引用计数没有增加。
对应的伪代码为:
  1. // src 栈 Block// dest 堆 Blockcopy_helper(id dest, id src) {  _Block_object_assign(&dest.byref, src.byref, 8);}_Block_object_assign(id *dest, id src, int flags) {  // 分配堆内存  *dest = malloc(sizeof(src.size));  // 其他给 dest 的赋值操作  ...  // 当前操作的是 Block_byref  if (flags & BLOCK_FIELD_IS_BYREF == BLOCK_FIELD_IS_BYREF) {    // 当前有 byref_keep    if (src.flags & BLOCK_BYREF_HAS_COPY_DISPOSE == BLOCK_BYREF_HAS_COPY_DISPOSE) {    src.byref_keep(*dest, src);        }}// dest 堆 Block_byref// src 栈 Block_byrefvoid byref_keep(id dest, id src) {  objc_storeStrong(&dest.byref_x, src.byref_x);  // 栈 Block_byref 中的 byref_x 置空  src.byref_x = nil;}
复制代码
由于后面剩余的情形,只有byref_keep不一样,因此如果没有必要,只会给出byref_keep的伪代码。
4.3.3 byref_keep 拷贝 Weak OC 对象

假如有如下代码:
  1. void blockTest() {  X *x = [X new];  __block __weak X *byref_wx = x;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + byref_wx.i;  };}
复制代码
上面代码定义了一个弱引用的OC对象byref_wx。
经过拷贝之后的内存布局为:

从图上可以看到,栈Block_byref对wx的弱引用被清空了。
对应的伪代码为:
  1. // dest 堆 Block_byref// src 栈 Block_byrefbyref_keep(id dest, id src) {  objc_moveWeak(&dest.wx, &src.wx);}
复制代码
byref_keep内部调用objc_moveWeak函数用来处理弱引用。
4.3.4 byref_keep 拷贝 Block 对象

假如有如下代码:
  1. void blockTest() {  int i = 5;  __block void(^byref_b)(void) = ^{    NSLog(@"%d", i);  };  void(^blk)(int, int, int) = ^(int i, int j, int k) {    byref_b();  };}
复制代码
上面代码定义了一个__block类型的Block b。
经过拷贝之后的内存布局为:

从图上可以看到,栈Block_byref对Block的引用并没有清空。
对应伪代码为:
  1. // dest 堆 Block_byref// src 栈 Block_byrefbyref_keep(id dest, id src) {  dest.byref_b = objc_retainBlock(src.byref_b);}id objc_retainBlock(id obj) {  return _Block_copy(obj);}
复制代码
从上面的代码可以看到,这种情形下的byref_keep最终会调用_Block_copy来拷贝__block类型的Block b。
4.3.5 byref_keep 拷贝弱引用 Block 对象

假如有如下代码:
  1. void blockTest() {  int i = 5;  void(^b)(void) = ^{    NSLog(@"%d", i);  };    __weak void(^byref_wb)(void) = b;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    byref_wb();  };}
复制代码
经过拷贝之后的内存布局为:

从图上可以看到,栈Block_byref的弱引用已经被清空了。
对应的伪代码为:
  1. // dest 堆 Block_byref// src 栈 Block_byrefbyref_keep(id dest, id src) {  objc_moveWeak(&dest.byref_wb, &src.byref_wb);}
复制代码
4.3.6 byref_keep 拷贝 C++  值对象

假如有如下代码:
  1. // C++ 类class Y {  public:  X *x; // OC 类  int i;};void blockTest() {  __block Y byref_y;  y.x = [X new];  y.i = 5;  void(^blk)(int, int, int) = ^(int i, int j ,int k) {    int result = i + j + k + byref_y.i;  };}
复制代码
上面代码定义了一个C++类Y以及一个__block变量byref_y。
讲过拷贝后的内存布局为:

从图上可以看到,栈Block_byref中C++对象对OC对象的引用已经清空了。
对应的伪代码为:
  1. // src 栈 Block_byref// dest 是堆 Block_byrefbyref_keep(id dest, id src) {  dest.byref_y = Y(&src.byref_y);}Y::Y(const Y *other) {  this->x = other->x;  other->x = nil;  this->i = i;}
复制代码
那如果C++对象内部引用的是一个Weak类型的OC对象呢?
比如有如下代码:
  1. class Y {  __weak X *wx; // OC 对象  int i;};
复制代码
讲过拷贝内存布局为:

从图上看,栈Block_byref中对wx的弱引用被清除了。
对应的伪代码为:
  1. // src 栈 Block_byref// dest 是堆 Block_byrefbyref_keep(id dest, id src) {  dest.byref_y = Y(&src.byref_y);}Y::Y(const Y *other) {  objc_moveWeak(&this->wx, &other->wx);  this->i = i;}
复制代码
4.3.7 byref_keep 拷贝 C++ 对象指针

假如有如下代码:
  1. class Y {  public:  X *x; // OC 对象  int i;};void blockTest() {  // C++  对象指针  __block Y *byref_y = new Y;  byref_y->x = [BlockK x];  byref_y->i = i;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + byref_y->i;  };}
复制代码
上面代码定义了一个C++类Y以及对应的__block变量byref_y。
这种情况下Block_byref是没有bref_keep的,它的flags的第28bit是1,代表BLOCK_BYREF_LAYOUT_NON_OBJECT。
经过拷贝后的内存布局为:

4.3.8 byref_keep 拷贝 C 结构体

假如有如下代码:
  1. typedef struct {  X *x; // OC 对象  int i;} Y;void blockTest() {  __block Y y;  y.x = [X new];  y.i = 5;  void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + y.i;  };}
复制代码
上面代码定义了一个C结构体Y以及__block变量byref_y。
经过拷贝之后的内存布局为:

对应的伪代码为:
  1. // src 栈 Block_byref// dest 是堆 Block_byrefbyref_keep(id dest, id src) {  move_constroctor(&dest.byref_y, &src.byref_y);}move_constructor(Y *dest, Y *src) {  this->x = src->x;  other->x = nil;  this->i = i;}
复制代码
move_constructor是编译器合成的函数。
那如果C结构体引用一个Weak类型的OC变量呢?
比如有如下代码:
  1. typedef struct  {  __weak X *wx; // OC 对象  int i;} Y;
复制代码
经过拷贝后的内存布局为:

从图上可以看到,栈Block_byref中对wx的弱引用被清除了。
对应的伪代码为:
  1. // src 栈 Block_byref// dest 是堆 Block_byrefbyref_keep(id dest, id src) {  move_constroctor(&dest.byref_y, &src.byref_y);}move_constructor(Y *dest, Y *src) {  objc_moveWeak(&dest->wx, &src->wx);  this->i = i;}
复制代码
4.3.9 byref_keep 拷贝 C 结构体指针

假如下面的代码:
  1. typdef struct {  X *x; // OC 对象  int i;} Y;void blockTest() {  // C 结构体指针  __block Y *bref_y = malloc(sizeof(Y));  bref_y->x = [X new];  bref_y->i = i;    void(^blk)(int, int, int) = ^(int i, int j, int k) {    int result = i + j + k + bref_y->i;  };}
复制代码
上面代码定义了一个C结构体Y以及__block变量byref_y。
这种情形和拷贝C++对象指针一样,Block_byref也没有byref_keep。
经过拷贝后的内存布局为:


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册