Block Copy 的内存布局详解

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

CopyBlock也很简单,直接将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的源码。

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

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

CopyBlock稍微复杂一点。

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

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

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

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

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

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

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

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

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

cpp 复制代码
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++对象指针的情形。

假如有如下的代码:

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

3步,设置堆Block_byref flags中的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

假如有下面的代码:

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_byrefforwarding指针也指向了堆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_byrefwx的弱引用被清空了。

对应的伪代码为:

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

对应伪代码为:

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

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

相关推荐
chaoguo12343 天前
__block 变量内存布局详解
block·__block
chaoguo12345 天前
Block 内存布局详解
block·内存布局
他们都不看好你,偏偏你最不争气10 天前
【iOS】block
开发语言·ios·objective-c·block·闭包
大熊猫侯佩2 个月前
Swift 6 驱魔实录:揭开 Combine 与 @Sendable 的“血色契约”
swift·block·combine·preconcurrency·sendable·mainactor·isolation
ergevv3 个月前
RK3588 上 OpenCV ROI 拷贝性能差异的根本原因与优化方案
opencv·计算机视觉·图像·image·clone·拷贝
RollingPin3 个月前
iOS探究使用Block方式实现一对多回调能力
ios·block·runtime·数据分发·解耦·动态绑定·一对多回调
IT成长日记8 个月前
【自动化运维神器Ansible】Ansible常用模块之Copy模块详解
运维·自动化·ansible·copy·常用模块、
IT成长日记8 个月前
【Docker基础】Dockerfile指令速览:基础常用指令详解
docker·容器·dockerfile·cmd·copy·from·run
kngines10 个月前
【PostgreSQL数据分析实战:从数据清洗到可视化全流程】4.4 异构数据源整合(CSV/JSON/Excel数据导入)
postgresql·数据分析·nifi·jq·copy·jsonb·talend