Block的Copy操作,都会调用到_Block_copy函数。
在LLVM工程源码runtime.c文件下给出了相关定义:
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 全局 Block
Copy全局Block最简单,直接返回全局Block本身,不做任何操作:
c
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中:
c
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:
c
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函数:
c
// 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_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环境下的一个标志位使用:
c
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:
c
BLOCK_REFCOUNT_MASK = (0xffff)
而是0xfffe。
这种情况,相当于把Block的引用计数整体向左移动了1bit。
因此,如果给引用计数加1,应该是加2。
换句话说,要计算Block的引用计数,通过掩码0xfffe取出值后,要右移1bit,也就是除以2。
比如通过掩码取出的值是4,实际的引用计数应该是2。
下面是对应的汇编码:
c
// 使用掩码 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, #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工程源码在如下:
c
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代码:
objectivec
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代码:
objectivec
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。
对应的伪代码如下:
objectivec
// 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代码:
objectivec
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,同时将这个弱引用记录到系统的弱引用表中。
对应的伪代码如下:
objectivec
// src 是栈 Block
// dest 是堆 Block
copy_helper(id dest, id src) {
objc_copyWeak(&dest.wx, &src.wx);
}
3.4 copy_helper 拷贝 Block 对象
假如有下面的OC代码:
objectivec
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来处理。
对应的伪代码如下:
objectivec
// 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中:
cpp
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 对象
假如有如下代码:
objectivec
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函数。
对应的伪代码为:
objectivec
// src 栈 Block
// dest 堆 Block
copy_helper(id dest, id src) {
objc_copyWeak(&dest.wb, &dest.src);
}
3.6 copy_helper 拷贝 C++ 值对象
假如有下面的代码:
objectivec
// 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合成了一个,伪代码如下:
c++
Y::Y(const Y *other) {
// 引用计数 +1
this->x = objc_retain(other->x);
this->i = other->i;
}
默认拷贝构造函数将C++对象中的OC成员变量x引用计数加1。
copy_helper的伪代码如下:
c
// src 是栈 Block
// dest 是堆 Block
copy_helper(id dest, id src) {
dest.y = Y::Y(&src.y);
}
那如果C++对象内部引用的是一个Weak类型的OC对象呢?
比如有如下代码:
c++
class Y {
__weak X *wx; // OC 对象
int i;
};
这种情形拷贝过程几乎一模一样。
唯一的区别是copy_helper函数调用的C++拷贝构造函数会使用objc_copyWeak函数,对应的伪代码如下:
cpp
// 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吗?比如:
cpp
// 不能这样声明
__weak Y y;
答案是不可以,因为__weak是OC才有的语义,C++下不支持。
另外,C++环境下的结构体struct和class本质上是一样的。
也就是说,下面代码定义完全一样:
cpp
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++对象指针的情形。
假如有如下的代码:
objectivec
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环境下的结构体。
假如有如下代码:
objectivec
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函数的伪代码如下:
c
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的伪代码如下:
c
// src 是栈 Block
// dest 是堆 Block
copy_helper(id dest, id src) {
construct_copy(&dest.y, &src.y);
}
那如果C结构体引用一个Weak类型的OC变量呢?
比如有如下代码:
c++
typedef struct {
__weak X *x; // OC 对象
int i;
} Y;
这种情形拷贝过程几乎一模一样。
唯一的区别是copy_helper函数调用的copy_construct会使用objc_copyWeak函数,对应的伪代码如下:
cpp
// 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:
c
// 不能这样声明
__weak Y y;
3.9 copy_helper 拷贝 C 结构体指针
第3.8节Block捕获的是C结构体自身,下面来看Block捕获C结构体指针的情形。
假如有如下的代码:
objectivec
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,那么就直接返回,什么也不做。
对应的伪代码为:
c
// 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
假如有下面的代码:
objectivec
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 对象
假如有如下代码:
objectivec
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的引用计数没有增加。
对应的伪代码为:
c
// 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 对象
假如有如下代码:
objectivec
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的弱引用被清空了。
对应的伪代码为:
c
// 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 对象
假如有如下代码:
objectivec
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的引用并没有清空。
对应伪代码为:
c
// 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类型的Block b。
4.3.5 byref_keep 拷贝弱引用 Block 对象
假如有如下代码:
objectivec
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的弱引用已经被清空了。
对应的伪代码为:
c
// 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++ 值对象
假如有如下代码:
objectivec
// 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对象的引用已经清空了。
对应的伪代码为:
c
// 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对象呢?
比如有如下代码:
c++
class Y {
__weak X *wx; // OC 对象
int i;
};
讲过拷贝内存布局为:
从图上看,栈Block_byref中对wx的弱引用被清除了。
对应的伪代码为:
c
// 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++ 对象指针
假如有如下代码:
objectivec
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 结构体
假如有如下代码:
objectivec
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。
经过拷贝之后的内存布局为:
对应的伪代码为:
c
// 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变量呢?
比如有如下代码:
c++
typedef struct {
__weak X *wx; // OC 对象
int i;
} Y;
经过拷贝后的内存布局为:
从图上可以看到,栈Block_byref中对wx的弱引用被清除了。
对应的伪代码为:
c
// 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 结构体指针
假如下面的代码:
objectivec
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。
经过拷贝后的内存布局为: