block 经常被用到,那么再底层它是如何被实现的,以及 相关的变量捕获和__block 是怎么处理的呢
准备工作
- clang 命令,将.m 转称.cpp
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m
block 的定义以及 外部对象的捕获
探索
定义一个block 的代码,并捕获外部函数,去研究一下这个过程执行了哪些操作
1.通过 Debug => Debug Workflow => Always show Disassembly 断点查看block 的调用
在定义对象时,汇编会调用到 objc_retainBlock
-> 0x100003d30 <+48>: movq %rcx, %rdi
0x100003d33 <+51>: callq 0x100003dc4 ; symbol stub for: objc_retainBlock
添加一个符号断点 Symbolic BreakPoint objc_retainBlock。 然后跳转执行
libobjc.A.dylib`objc_retainBlock:
-> 0x7fff727aff83 <+0>: jmp 0x7fff727c6988 ; symbol stub for: _Block_copy
可以看到会执行 _Block_copy 的符号,另外在libobjc objc 的源码中查看 objc_retainBlock 的代码,会执行 _Block_copy函数
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
添加一个 符号断点 Symbolic BreakPoint _Block_copy 可以找到
libsystem_blocks.dylib`_Block_copy:
-> 0x7fff739ab96b <+0>: pushq %rbp
后续可以通过 Hopper 取反汇编 libsystem_blocks 查看 _Block_copy 的代码。
这里获取到了 libclosure 提取码:ucu1 用来学习
_Block_copy 函数
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
// 将参入参数转换为 Block_layout 的结构体指针
aBlock = (struct Block_layout *)arg;
// 判断block的flag 是否 需要释放
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// BLOCK_IS_GLOBAL 是在编译器确定的,如果时 全局的就返回,不处理
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock; // 不需要
}
else {
// 如果是 栈函数 进行处理
// Its a stack block. Make a copy.
// 创建一个 malloc 的空间,将 栈区 的数据拷贝的 堆 中
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
// 重置 refcount 的数量 以及 Deallocating 标志 为0 & ~ 。
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 设置了 BLOCK_NEEDS_FREE 以及 BLOCK_DEALLOCATING 的数据
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// 代码块在编译时,当有捕获外部对象时,会在编译时,生成一个Block_Byref的结构体对象,用来存储相关的捕获对象的数据
// _Block_call_copy_helper 这里就是将相关的数据 从栈区的数据 拷贝到 堆区的逻辑
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
- 会将传入的arg参数转换为 Block_layout 的结构体指针。说明 Block_layout 就是block 的结构。flag 会再编译器确定一些数据。
- BLOCK_NEEDS_FREE 是在运行时确定的数据。 第一次进入是会是0.然后再 stack 拷贝到 malloc 之后,会设置。其实就是如果已经堆区的block。对其的操作就只即将引用计数增加
- BLOCK_IS_GLOBAL 全局的 block 就直接返回,不做处理
- 处理栈的block
- 分配堆的空间,然后将栈中的block 数据拷贝到堆
- 设置 函数指针,
- 重置 BLOCK_REFCOUNT_MASK 以及 BLOCK_DEALLOCATING 的数据
- 以及设置 上面提到的 BLOCK_NEEDS_FREE
- 执行 _Block_call_copy_helper 对捕获的外部逻辑的处理
- 将isa 指向 _NSConcreteMallocBlock。
- 返回新创建的 堆区 的block
主要三个部分
- 如果已经是堆区的block( BLOCK_NEEDS_FREE) 就处理引用计数
- 如果时全局的block 不去处理
- 如果时stack block 就将stack 处理拷贝到 堆区转换成 malloc block
latching_incr_int
主要是对 BLOCK_REFCOUNT数据的处理。可以看下flag 的位域布局。 BLOCK_REFCOUNT 是从第二位开始,加一就是 flag + 2
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
// 如果数据就是掩码,就是美数据
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
// 原子操作, 比较和交换
// 加2 是因为 最低的标识为是 BLOCK_DEALLOCATING,引用计数的位域是从第2位开始,所以flag + 2 其实是 引用计数加1
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2; //
}
}
}
#define OSAtomicCompareAndSwapInt(_Old, _New, _Ptr) __sync_bool_compare_and_swap(_Ptr, _Old, _New)
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime 是否在 deallocating 1个位域
// 0xfffe 0b1111 1111 1111 1110 引用计数的掩码,就是 2到16 位域的数据
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 从0x2开始到 0xfffff 的位域 0x1 表示的是deallocating
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler 是否是 Global
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler 是否有签名 -> hook 用
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
_Block_call_copy_helper
- 获取到 block 中的desc (在编译器确定的数据),然后执行一次。根据相关结构体的定义以及使用xrun 半年以后的cpp文件, 可以找到相关的 copy 函数和 dispose 函数
- 编译中掉哟蝈
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
// 先去判断有没有 descriptor2 就是有没有 拷贝和释放函数
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
// 会调用编译器自动生成的copy 函数,
(*desc->copy)(result, aBlock); // do fixup
}
// 例子
static void __main_block_copy_1(struct __main_block_impl_1*dst, struct __main_block_impl_1*src){
// scr->p1 就是定义的代码块中 捕获的参数
_Block_object_assign( (void*)&dst->p1, (void*)src->p1, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->p1, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->p2, 8/*BLOCK_FIELD_IS_BYREF*/);
}
- 在block 的copy函数中,对于每一个捕获的变量,编译器都会生成一个。 最后一个是 捕获对象的类型 _Block_object_dispose((void)src->p1, 3/BLOCK_FIELD_IS_OBJECT*/);
- 在block的 despose 函数中,则会对每一个变量执行 _Block_object_dispose 函数, 同样参入参数的类型
_Block_object_assign
// block 捕获变量的类型标志
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block,
...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
// 仅对上面面的几种类型的参数进行操作
enum {
BLOCK_ALL_COPY_DISPOSE_FLAGS =
BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
};
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
// __block 变量 destArg malloc取的对象指针地址, 栈区block 补货变量的指针, flag
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
// 仅对不会类型的参数进行操作 对象, block,__block 定义的变量,__weak, de duixiang
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
/******* 类似
id object = ...;
[^{ object; } copy];
********/
// objc 指针地址 weakSelf (self)
// arc
_Block_retain_object(object);
// 持有
// 将objc的指针存入到 堆取复制对象的地址。有一个引用
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// block 被一个 block 捕获
// 那么被捕获的block 也要进行 _Block_copy 一边
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: //
case BLOCK_FIELD_IS_BYREF: // 如果是 __block 修饰的变量
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
// 执行 __ _Block_byref_copy
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
// 直接获取地址
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
// 获取地址
*dest = object;
break;
default:
break;
}
}
_Block_byref_copy
在内存中创建 __block 修饰对象编译时 创建的结构体,然后拷贝
static struct Block_byref *_Block_byref_copy(const void *arg) {
// Block_byref 结构体
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
// 创建新的copy
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
//设置 BLOCK_BYREF_NEEDS_FREE
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// 问题 - block 内部 持有的 Block_byref 锁持有的对象 是不是同一个
// 这里就是 __block 修饰后 外部变量和内部捕获变量一致所达到的方式,,src 的forwarding 指向的时新创建的 malloc 数据。
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
// 是否有copy 函数, 有就移动 key 函数以及个desctory 函数,
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
// 在判断 移动 Block_byref_3 的数据
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
// 执行定义的keep 方法(编译确定)
(*src2->byref_keep)(copy, src);
}
else {
// 没有就直接拷贝
// 对于一下在栈区的数据,拷贝到堆区存储
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
// 如果已经是在内存中 (即之前已经在内存的数据里,就增加引用计数 -> 比如一个 __block 修饰的对象被多个block捕获)
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
- 如果 BLOCK_BYREF_HAS_COPY_DISPOSE 的情况,回去找到并调用 keep 方法
通过 xrun 的命令,然后编译一个 捕获了 __block修饰的变量,然后就能找到 keep 和 dispose 函数的定义。这里是在 BLOCK_BYREF_HAS_COPY_DISPOSE 情况下。 根据编译的方法,会对捕获的对象数据调用_Block_object_assign 方法
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
- 如果没有 copy dispose 方法,则会之间创建一个内存地址,并拷贝复制 栈对应的数据
_Block_copy 总结:
- block 的拷贝: 当将block 复制给一个变量时,会执行 _Block_copy => 在堆创建 Block_layout 结构体,将栈区的数据拷贝进去
- 引入变量的 assign: 然后判断 Block_layout 变量是否有 编译确定的copy 函数,有的话,去执行,编译确定的copy函数其实就是 一次执行对所有拷贝变量的 _Block_object_assign. -> 对象就是直接引用,
- 引入的变量 如果是 __block 修饰的 变量,就会在编译器生成一个 Block_byref 的结构体类型,然后执行_Block_byref_copy 其中 的forward 操作是实现代码块内部可以修改外部数据的实现(因为外部访问的其实是forword 的指向的数据) (对于简单数据类型,直接复制新的内存数据)
- 其他的就是再对象地址上的 写入 传入的指针地址 或者 简单类型数据; *dest = object; 操作
相关结构体的定义
Block_layout
block 对应的结构体
struct Block_layout {
void *isa; // 对象指针 global / malloc / statck 的 block类
volatile int32_t flags; // contains ref count 包含block的 一些信息
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; // 结构体的描述,关于签名,变量捕获相关的数据
// imported variables
// 后续的是引入的参数,就会根据
};
// block 的flag 中存储的信息,
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime 是否在 deallocating
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler 是否是 Global
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler 是否有签名 -> hook 用
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
// descriptor 指针指向的地址 其中size 时整个Block_descriptor的大小,后续的内存可能时 BLOCK_DESCRIPTOR_2 也可能时 BLOCK_DESCRIPTOR_3 在编译时确定
struct Block_descriptor_1 {
uintptr_t reserved;
// Block_descriptor_1 对象分配的内存大小。
// 因为可能包含着 Block_descriptor_2 Block_descriptor_3 的数据
uintptr_t size;
};
// 可选
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
// 可选
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
可以配合xrun命令去理解 compiler 确定的一些数据
Person *p1 = [[Person alloc] init];
Block b2 = ^{
NSLog(@"12");
p1.name = @"asd";
};
// 编译生成的数据
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
Person *p1;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, Person *_p1, int flags=0) : p1(_p1) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
Person *p1 = __cself->p1; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gw_flhw88953gg0m9rcpg_y974w0000gn_T_main_8c5857_mi_1);
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p1, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_gw_flhw88953gg0m9rcpg_y974w0000gn_T_main_8c5857_mi_2);
}
static void __main_block_copy_1(struct __main_block_impl_1*dst, struct __main_block_impl_1*src) {_Block_object_assign((void*)&dst->p1, (void*)src->p1, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_1(struct __main_block_impl_1*src) {_Block_object_dispose((void*)src->p1, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_1*, struct __main_block_impl_1*);
void (*dispose)(struct __main_block_impl_1*);
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1), __main_block_copy_1, __main_block_dispose_1};
Block_byref
__block 修饰的变量 会在编译器编译成一个Block_byref 的结构体。
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime 是否正在析构
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 引用数的mask
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler 编译器确定
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; // 结构体 __block 对象
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
__block 的实现
__block 修饰的对象,会在变异期间,生成一个对应的 Block_byref 结构体数据,其对应的是 BLOCK_FIELD_IS_BYREF 类型。
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *a;
};
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
(void*)0, // isa
(__Block_byref_a_0 *)&a, // foawrd
33554432, // flag
sizeof(__Block_byref_a_0), // size
__Block_byref_id_object_copy_131, // byref_keep 函数 Block_byref_2
__Block_byref_id_object_dispose_131, // byref_destroy Block_byref_2
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")) // 被捕获的对象数据 [[NSObject alloc] init]
};
而在外部调用 这个变量使用forward 转一层去获取
NSLog((NSString *)&__NSConstantStringImpl__var_folders_lr_r085xz2d6jjbsxfv0mq9qh5m0000gn_T_main_47e798_mi_2,(a.__forwarding->a));
所以__block 修饰的变量,只要代码块 内外的不同变量有相同的 forward 就可以是外部数据和代码块内部数据保持一致。就达到了代码块可以修改外部变量的效果。
可以参考上面 _Block_byref_copy 的源码
// 问题 - block 内部 持有的 Block_byref 锁持有的对象 是不是同一个
// 这里就是 __block 修饰后 外部变量和内部捕获变量一致所达到的方式,,src 的forwarding 指向的时新创建的 malloc 数据。
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
网友评论