Block Copy 的内存布局详解

Block Copy 的内存布局详解 Block的Copy操作都会调用到_Block_copy函数。在LLVM工程源码runtime.c文件下给出了相关定义: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 全局 BlockCopy全局Block最简单直接返回全局Block本身不做任何操作: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中:struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };本质上就是Block结构体不包含捕获变量的部分。2 Copy 堆 BlockCopy堆Block也很简单直接将Block自身的引用计数加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函数:// runtime.c static 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_value1, (volatile int *)where)) { // 引用计数值加 1 return old_value1; } } }如果Block本身的引用计数到达了最大值BLOCK_REFCOUNT_MASK那么直接返回这个最大值否则Block自身的引用计数加1。但是经过测试现实中的实现并不总是遵循LLVM的源码。Block的flags低15bit并不都是用来进行引用计数的。flags中的最低位也就是第0bit不参与ARC环境下的引用计数。看LLVM源码推测它是作为GC环境下的一个标志位使用: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:BLOCK_REFCOUNT_MASK (0xffff)而是0xfffe。这种情况相当于把Block的引用计数整体向左移动了1bit。因此如果给引用计数加1应该是加2。换句话说要计算Block的引用计数通过掩码0xfffe取出值后要右移1bit也就是除以2。比如通过掩码取出的值是4实际的引用计数应该是2。下面是对应的汇编码:// 使用掩码 0xfffe 获取 flags 中的值 - 0x18013f93c 192: mov w8, #0xfffe ; 65534 0x18013f940 196: ldr w9, [x20, #0x8] 0x18013f944 200: bics wzr, w8, w9 0x18013f948 204: b.eq 0x18013f964 ; 232 // 将引用计数加 1就是加 2 0x18013f94c 208: add w10, w9, #0x23 Copy 栈BlockCopy栈Block稍微复杂一点。第1步需要在堆上创建一个和栈Block同样大小的堆Block。第2步将栈Block结构体里的值原封不动的拷贝到新创建的堆Block。第3步设置堆Blockflags中的BLOCK_NEEDS_FREE标志位。第4步设置堆Block的引用计数为1。第5步看当前堆Block有没有copy_helper函数有的话就执行。第6步设置堆Block的isa指针为MallocBlock。相关的LLVM工程源码在如下: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代码: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代码: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。对应的伪代码如下:// src 是栈 Block // dest 是堆 Block copy_helper(id dest, id src) { dest.x nil; objc_storeStrong(dest.x, src.x); }注意上面代码拷贝的时候仅仅是增加了对象x的引用计数而没有对对象x也进行拷贝。换句话说Block执行的是浅拷贝。3.3 copy_helper 拷贝 Weak OC 对象假设有下面的OC代码: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同时将这个弱引用记录到系统的弱引用表中。对应的伪代码如下:// src 是栈 Block // dest 是堆 Block copy_helper(id dest, id src) { objc_copyWeak(dest.wx, src.wx); }3.4 copy_helper 拷贝 Block 对象假如有下面的OC代码:void blockTest() { int i 5; void(^b)(void) ^{ NSLog(%d, i); }; void(^blk)(int, int, int) ^(int i, int j, int k) { b(); }; }上面代码中Blockblk捕获了另一个Blockb。由于拷贝操作的前5步将栈Block内容原封不动复制到了堆Block这样导致对Blockb的引用增加了但是对Blockb的引用计数确没有增加。在这种情况下copy_helper会调用_Block_copy函数拷贝Blockb。在ARC环境下Blockb已经存在堆上了。根据前面所述_Block_copy只会增加Blockb的引用计数。最后让堆Block指向Blockb。整个拷贝流程和拷贝StrongOC对象类似。区别是捕获的OC对象调用objc_storeStrong处理而捕获的Block调用_Block_copy来处理。对应的伪代码如下:// src 栈 Block // dest 堆 Block copy_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中: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 对象假如有如下代码: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捕获了一个弱引用的Blockwb。这种情形和copy_helper拷贝弱引用的OC对象类似。copy_helper会调用objc_copyWeak函数。对应的伪代码为:// src 栈 Block // dest 堆 Block copy_helper(id dest, id src) { objc_copyWeak(dest.wb, dest.src); }3.6 copy_helper 拷贝 C 值对象假如有下面的代码:// 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合成了一个伪代码如下:Y::Y(const Y *other) { // 引用计数 1 this-x objc_retain(other-x); this-i other-i; }默认拷贝构造函数将C对象中的OC成员变量x引用计数加1。copy_helper的伪代码如下:// src 是栈 Block // dest 是堆 Block copy_helper(id dest, id src) { dest.y Y::Y(src.y); }那如果C对象内部引用的是一个Weak类型的OC对象呢比如有如下代码:class Y { __weak X *wx; // OC 对象 int i; };这种情形拷贝过程几乎一模一样。唯一的区别是copy_helper函数调用的C拷贝构造函数会使用objc_copyWeak函数对应的伪代码如下:// src 是栈 Block // dest 是堆 Block copy_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吗比如:// 不能这样声明 __weak Y y;答案是不可以因为__weak是OC才有的语义C下不支持。另外C环境下的结构体struct和class本质上是一样的。也就是说下面代码定义完全一样: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对象指针的情形。假如有如下的代码: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环境下的结构体。假如有如下代码: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函数的伪代码如下: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的伪代码如下:// src 是栈 Block // dest 是堆 Block copy_helper(id dest, id src) { construct_copy(dest.y, src.y); }那如果C结构体引用一个Weak类型的OC变量呢比如有如下代码:typedef struct { __weak X *x; // OC 对象 int i; } Y;这种情形拷贝过程几乎一模一样。唯一的区别是copy_helper函数调用的copy_construct会使用objc_copyWeak函数对应的伪代码如下:// src 是栈 Block // dest 是堆 Block copy_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:// 不能这样声明 __weak Y y;3.9 copy_helper 拷贝 C 结构体指针第3.8节Block捕获的是C结构体自身下面来看Block捕获C结构体指针的情形。假如有如下的代码: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那么就直接返回什么也不做。对应的伪代码为:// 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_byrefflags中的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假如有下面的代码: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 对象假如有如下代码: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的引用计数没有增加。对应的伪代码为:// src 栈 Block // dest 堆 Block copy_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_byref void 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 对象假如有如下代码: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的弱引用被清空了。对应的伪代码为:// dest 堆 Block_byref // src 栈 Block_byref byref_keep(id dest, id src) { objc_moveWeak(dest.wx, src.wx); }byref_keep内部调用objc_moveWeak函数用来处理弱引用。4.3.4 byref_keep 拷贝 Block 对象假如有如下代码: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类型的Blockb。经过拷贝之后的内存布局为:从图上可以看到栈Block_byref对Block的引用并没有清空。对应伪代码为:// dest 堆 Block_byref // src 栈 Block_byref byref_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类型的Blockb。4.3.5 byref_keep 拷贝弱引用 Block 对象假如有如下代码: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的弱引用已经被清空了。对应的伪代码为:// dest 堆 Block_byref // src 栈 Block_byref byref_keep(id dest, id src) { objc_moveWeak(dest.byref_wb, src.byref_wb); }4.3.6 byref_keep 拷贝 C 值对象假如有如下代码:// 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对象的引用已经清空了。对应的伪代码为:// src 栈 Block_byref // dest 是堆 Block_byref byref_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对象呢比如有如下代码:class Y { __weak X *wx; // OC 对象 int i; };讲过拷贝内存布局为:从图上看栈Block_byref中对wx的弱引用被清除了。对应的伪代码为// src 栈 Block_byref // dest 是堆 Block_byref byref_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 对象指针假如有如下代码: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 结构体假如有如下代码: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。经过拷贝之后的内存布局为:对应的伪代码为:// src 栈 Block_byref // dest 是堆 Block_byref byref_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变量呢比如有如下代码:typedef struct { __weak X *wx; // OC 对象 int i; } Y;经过拷贝后的内存布局为:从图上可以看到栈Block_byref中对wx的弱引用被清除了。对应的伪代码为:// src 栈 Block_byref // dest 是堆 Block_byref byref_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 结构体指针假如下面的代码: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。经过拷贝后的内存布局为: