在 juejin.cn/post/699877… 节中我们研究了block的一些现象,这节我们来从源码的角度分析block,看看它里面是怎么实现的。
clang看block
#include "stdio.h"
int main(){
void(^block)(void) = ^{
printf("这是一个block");
};
block();
return 0;
}
用clang来查看源码
void(*block)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
));
我们看一下__main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
block中传入的第一个参数为__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("这是一个block");
}
这是一个函数,第一个参数是函数地址,传过去一个函数 block的调用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
__block_impl的结构如下
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
去掉强制转换block->FuncPtr(block)即调用__main_block_func_0函数,传参为block
block捕获变量
#include "stdio.h"
int main(){
int a =0;
void(^block)(void) = ^{
printf("这是一个block%d", a);
};
block();
return 0;
}
clang之后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; //1
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {//2
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy //3
printf("这是一个block%d", a);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(){
int a =0;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
1.捕获了变量之后,block里面有一个变量int a
2.block调用的时候block->FuncPtr(block)传入block
int a = __cself->a; 函数调用中的a 和外面的a 值相同,地址不同,
📢我们知道这是一个堆block,但是 impl.isa = &_NSConcreteStackBlock;提示它是一个栈block,我们还没有运行,那么什么时候它变成了堆block,中间做了什么呢
所以修改的话,会报错,我们知道,需要使用__block修饰变量a,才能在block里面修改a
被__block修饰
int main(){
__block int a = 0;
void(^block)(void) = ^{
a = 5;
printf("这是一个block%d", a);
};
block();
return 0;
}
clang之后
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 5;
printf("这是一个block%d", (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(){
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_a_0 *)&a,
570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_a_0 *)&a,
570425344));
我们可以看到block的第三个参数为(__Block_byref_a_0 *)&a,被__block修饰之后,a变成了__Block_byref_a_0
从__Block_byref_a_0中可以看出
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
__attribute__((__blocks__(byref))) __Block_byref_a_0 a =
{
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
0
};
a指向了__forvarding
从函数的调用看
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_a_0 *)&a,
570425344));
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 5;
printf("这是一个block%d", (a->__forwarding->a));
}
传入参数为block,block中传入的是(__Block_byref_a_0 *)&a a的地址,所以调用的时候__Block_byref_a_0 *a = __cself->a;里面的a和外面的a指向的是同一个a。可以被修改。
_Block_copy
如图打一个断点,我们看下汇编,
我们看到里面调用了
objc_retainBlock
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
我们看下_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
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy. -> heap
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 -- 对象 isa 联合体位于
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
我们看到,如果block在栈上,则进行copy到堆上面,然后result->isa = _NSConcreteMallocBlock;
Block的结构
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
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
};
// KC注释:Block 结构体
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
block在底层是一个结构体,我们看到Block_descriptor_3里面有signature在一些block的hook中,我们会拿到block的签名,先保存起来,在消息转发的合适时机,触发签名的调用,实现block的hook。
BLOCK_DESCRIPTOR_2、BLOCK_DESCRIPTOR_3、BLOCK_DESCRIPTOR_1他们的关系是怎么样的呢,我们找到他们的取值函数
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
// KC注释: Block 的描述 : 签名相关
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
Block_descriptor_2 中会判断是否有BLOCK_HAS_COPY_DISPOSE标记,然后 desc += sizeof(struct Block_descriptor_1);偏移了Block_descriptor_1的大小。同理Block_descriptor_3。可见他们的地址是连续的,来看一下这个标记
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
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
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};