当前位置: 首页 > news >正文

Block Copy 的内存布局详解

BlockCopy操作,都会调用到_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 全局 Block

Copy全局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 堆 Block

CopyBlock也很简单,直接将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_value+1, (volatile int *)where)) { // 引用计数值加 1 return old_value+1; } } }

如果Block本身的引用计数到达了最大值BLOCK_REFCOUNT_MASK,那么直接返回这个最大值;否则,Block自身的引用计数加1

但是经过测试,现实中的实现,并不总是遵循LLVM的源码。

Blockflags15bit并不都是用来进行引用计数的。

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, #0x2

3 Copy 栈Block

CopyBlock稍微复杂一点。

1步,需要在堆上创建一个和栈Block同样大小的堆Block

2步,将栈Block结构体里的值,原封不动的拷贝到新创建的堆Block

3步,设置堆Blockflags中的BLOCK_NEEDS_FREE标志位。

4步,设置堆Block的引用计数为1

5步,看当前堆Block有没有copy_helper函数,有的话就执行。

6步,设置堆Blockisa指针为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;

答案是不可以,因为__weakOC才有的语义,C++下不支持。

另外,C++环境下的结构体structclass本质上是一样的。

也就是说,下面代码定义完全一样:

class Y { public: X *x; // OC 对象 int i; }; struct Y { public: X *x; // OC 对象 int i; };

3.7 copy_helper 拷贝 C++ 对象指针

3.6Block捕获的是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++环境下的sturctclass等价,这里说的结构体专指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.8Block捕获的是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_byrefisa设置为0

3步,设置堆Block_byrefflags中的BLOCK_NEEDS_FREE标志位,表明已经在堆上了。

4步,设置堆Block_byref的引用计数为2

为什么不是设置为1呢?后面就可以看到原因。

5步,将堆Block_byreffowarding指针指向堆Block_byref自身。

6步,将栈Block_byreffowarding指向堆Block_byref

这就是为什么第3步要设置堆Block_byref的引用计数为2的原因。

因为除了堆Block指向堆Block_byref之外,栈Block_byrefforwarding指针也指向了堆Block_byref

7步,判断栈Block_byref有没有byref_keep函数。

如果没有,那么直接将栈Block_byref剩余部分原封不动的复制到堆Block_byref对应的位置。

如果栈Block_byrefbyref_keep函数,那么将byref_keepbyref_destroy复制到堆Block_byref

接下来,要判断栈Block_byrefflags中,有没有设置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的作用,和Blockcopy_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_byrefforwarding指针也指向了堆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_byrefwx的弱引用被清空了。

对应的伪代码为:

// 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_byrefBlock的引用并没有清空。

对应伪代码为:

// 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_byrefC++对象对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的第28bit1,代表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

经过拷贝后的内存布局为:

http://www.cnnetsun.cn/news/1449088.html

相关文章:

  • OpCore-Simplify:让黑苹果配置从复杂到简单的革命性工具
  • Windows 11下OpenVINO 2022.1保姆级安装指南(AMD CPU实测可用)
  • STM32平台VL53L7CX多区ToF传感器驱动库详解
  • kotlin:函数式参数
  • 告别拖拽对齐的折磨,分享一个 AI 驱动的架构图生成器 ArchGen
  • 本地 LLM 部署指南 - 隐私优先的 AI 开发环境搭建
  • 别再手动K帧了!用Mixamo+Unity 2022快速搞定3D角色动画(附完整项目文件)
  • 深入理解java多线程技术
  • CAE软件市场发展态势及优质代理商——今宏科技实践解析
  • 基于Qwen3-ForcedAligner-0.6B的语音合成前端优化方案
  • 当AI开始“做科研“:从万名爱因斯坦到全自动实验室,人类还剩什么?
  • Qwen3-TTS-Tokenizer:12Hz极致压缩语音编解码工具
  • OpenCV实战:LSD直线检测两种实现对比(附Python/C++代码)
  • Local SDXL-Turbo部署指南:Autodl中设置定时快照防止意外中断损失
  • wwwww
  • 如何解决华硕ROG笔记本色彩配置丢失问题:G-Helper高效恢复GameVisual设置实用指南
  • Java学习笔记_Day12
  • 保姆级教程:用Python从零复现Pan-Tompkins算法(含MIT-BIH数据库验证)
  • 基于Astar算法的智能小车路径规划模型:详细注释与参考文献附送
  • WiFi标签管理系统功能清单
  • 2026知识付费SaaS平台实测对比:创客匠人综合首选,真实数据说话
  • ADS1X58库详解:TI ADS1258/ADS1158高精度Σ-Δ ADC驱动实践
  • GME-Qwen2-VL-2B-Instruct与计算机组成原理教学:可视化理解CPU流水线
  • leetcode 1470. Shuffle the Array 重新排列数组-耗时100
  • RMBG-2.0快速入门:10分钟掌握背景移除技术
  • 用 OpenClaw + 微信实现 AI 自动回复(附完整接入流程)
  • Youtu-2B非遗文化问答:数字化保护系统搭建教程
  • AI创作春联实测:春联生成模型-中文-base生成效果展示与技巧
  • Stable Yogi 模型DevOps实践:Linux环境下的持续集成与监控
  • 如何选择合适的石英晶振用于频率仪表?