一直使用Block,却没有认真研究过,简单记录一下。
Block的实质就是匿名函数,通过函数指针的调用来实现的,并对内部的引用到的数据进行管理(retain/release),封装后成为Block,最终变成对象。
1.Block结构
void (^block)(void) = ^(void) {
printf("hello world");
};
通过clang -rewrite-objc filename 命令编译转换后:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world");
}
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(){
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
大致就是通过调用指向匿名函数的函数指针来实现block,__block_impl存放着实现block的数据结构,上面是最基本的结构。
当block持有了变量,数据结构就会发生一些改变,变化体现在在struct __main_block_impl_0和struct __main_block_desc_0。
1.1
当是一个基本数据变量时,在__main_block_impl_0里面会加入一个新的变量,比如当block使用了age变量
printf("hello world:%d",age);//age 为外部变量
转换后的结构为:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
printf("my age is:%d", age);
}
Desc变量后面多个一个int age的变量。
1.2
当Block中引用了对象时,不仅有上面的变化,__main_block_desc_0也会有变化
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};
从上面可以看出,多了个copy和dispose函数,这俩其实就是retain和release,用来处理持有的对象,定义如下
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, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}
注释中也写明了是对象。
1.3
当引用了__block修饰的变量,比如block使用了__block NSOject *obj = [NSObject new],则会有一个新的数据结构体来表示__block修饰的变量,那就是__Block_byref结构体,
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
可以看到最后有个NSObject类型的obj变量。
而__main_block_impl_0中的变量属性则会变成上面结构体类型,结构体的名字和变量名有关。
__Block_byref_obj_0 *obj; // by ref
block_desc结构体中也发生变化
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};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
多了个copy和dispose函数,函数调用时的参数是BLOCK_FIELD_IS_BYREF,表明在copy/release时处理block_impl中的obj是byref类型。
而且在对__main_block_impl_0的变量进行初始化时,传递的obj值是这样的初始化的
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
可以看到,Block_byrey结构体中的obj变量,还是通过objc_msgSend方式实现的,flag是33554432,代表BLOCK_HAS_COPY_DISPOSE,同时将__Block_byref_id_object_copy_131传给了__Block_byref_id_object_copy,__Block_byref_id_object_dispose_131传给了__Block_byref_id_object_dispose,这两个函数的定义如下
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);
}
也是通过_Block_object_assign和_Block_object_dispose来管理变量的,参数131代表BLOCK_BYREF_CALLER(128)+BLOCK_FIELD_IS_OBJECT(3)。
当__block修饰的是基本数据类型时,则__Block_byref中没有copy和dispose函数。至于为何要多一个__forwarding的处理,我们后续再说。
2 Block的类型
常见的类型分为三种,全局__NSGlobalBlock__,栈__NSStackBlock__,堆__NSMallocBlock__,
NSStackBlock__类型copy时会变为__NSMallocBlock,__NSMallocBlock__类型copy时,对引用计数+1。
__NSGlobalBlock__类型是block内部不引用任何变量或全局block,对该类型copy或release时返回原值。
__NSGlobalBlock__型
void (^block)(void) = ^(void){
NSLog(@"hello world");
};
NSMallocBlock__型,在arc下赋值的时会自动插入objc_retainBlock,所以会变成__NSMallocBlock,如果在非ARC下就是__NSStackBlock__型
int a = 0;
void (^block1)(void) = ^(void){
NSLog(@"hello world,%d",a);
};
NSStackBlock 型
int a = 0;
__unsafe_unretained void (^block1)(void) = ^(void){
NSLog(@"hello world,%d",a);
};
NSLog(@"%@",^(void){
NSLog(@"hello world,%d",a);}
);
各自的继承关系
__NSStackBlock__继承自__NSStackBlock,__NSStackBlock继承自NSBlock,NSBlock继承自NSObject。
__NSMallocBlock__继承自__NSMallocBlock,__NSMallocBlock继承自NSBlock。
__NSGlobalBlock__继承自__NSGlobalBlock,__NSGlobalBlock继承自NSBlock。
__NSStackBlock、__NSMallocBlock、__NSGlobalBlock和NSBlock定义在CoreFoundation,
// 570425344 = 536870912(BLOCK_USE_STRET) + 33554432(BLOCK_HAS_COPY_DISPOSE)
// 1342177280 = 1073741824(BLOCK_HAS_SIGNATURE) + 268435456(BLOCK_IS_GLOBAL)
// 1375731712 = BLOCK_HAS_SIGNATURE(1073741824) + BLOCK_IS_GLOBAL(268435456) + BLOCK_HAS_COPY_DISPOSE(33554432)
3 一个完整的流程
通常我们对Block的copy实际是调用了NSBlock的copy方法,而它的copy方法也很简单,直接调用了libsystem_blocks.dylib中的_Block_copy。这是专门来处理block的框架,代码是开源的。
先看下flag的定义
// Values for Block_layout->flags to describe block objects
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
};
BLOCK_IS_GLOBAL表示Global,BLOCK_NEEDS_FREE表示Malloc。
和blocks框架中的结构体定义
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
#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
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
对比上面的clang转换后的代码,我们发现不同的是一些变量在不同的结构体中,但总体数据的顺序是一致的,这种定义更直观一些。
_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);//增加引用计数flags+2
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;//Gbloal直接返回
}
else {
// Its a stack block. Make a copy.
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
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;
}
}
对Global和Malloc类型的处理都比较简单,对Stack类型的处理复杂一些,因为可能涉及到对持有变量和block自身的复制。
首先是malloc分配内存区域,然后复制size大小的数据到新的block中,将flag设置为BLOCK_NEEDS_FREE,表示是Malloc类型,并将flag加2,表示引用计数加1,然后调用_Block_call_copy_helper函数处理其他数据,最后将isa置为_NSConcreteMallocBlock,然后然后新的block。
// 如果有copy和dispose,就返回
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;
}
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); // do fixup
}
对于只持有基本数据类型的block来说,是没有copy和dispose的,所以_Block_call_copy_helper里面什么也没做。对于持有对象的block来说,最终走到copy函数。以上文的1.2为例,其copy函数为_Block_object_assign,函数共有三个参数,最后一个为3/BLOCK_FIELD_IS_OBJECT/。
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// 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];
********/
*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;
}
}
根据case判断,会执行_Block_retain_object,在开源代码中,_Block_retain_object其实就是_Block_retain_object_default,而后者是空函数,实际上肯定不会搞一个这样的空函数,代码中开源隐藏了一些代码实现。
static void _Block_retain_object_default(const void *ptr __unused) { }
static void _Block_release_object_default(const void *ptr __unused) { }
static void _Block_destructInstance_default(const void *aBlock __unused) {}
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;
static void (*_Block_destructInstance) (const void *aBlock) = _Block_destructInstance_default;
在Xcode中打上_Block_object_assign符号断点,其中有call _Block_retain_object的指令,追踪进去,直接变为libobjc.A.dylib中的objc_retain,开源代码中确实隐藏了一些信息。
对于持有的变量是block,则再执行_Block_copy,就是copy一下block,此处禁止套娃😂。
_Block_byref_copy函数是来处理持有__block修饰的变量。
我们以__block修饰的基本数据类型变量为例。上面我们说过在这种情况下,生成的__Block_byref结构体内是没有copy和dispose函数的,
还是先对bloack进行了copy。然后执行_Block_call_copy_helper,获取到copy函数,即_Block_object_assign。完整调用是
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
在函数内部会执行到,其将copy后的结果返回
*dest = _Block_byref_copy(object);
函数内部
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
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;
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;
}
(*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
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
1,整个流程先判断是否在堆中,是则调用latching_incr_int累加引用计数,否则进行copy。对flag加上BLOCK_BYREF_NEEDS_FREE,表示在堆中,再加上4,表示2个引用。关键代码则是forwarding的赋值,都指向了copy。在block代码中使用到的__block变量,其实都是通过obj->__forwarding->obj这种方式使用的,这样保证了原block释放后,后续copy的block持有的__block的变量也不受影响。
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
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
可以看出和__Block_byref结构体相同,不过是分成了3个。Block_byref_2是可能存在的。Block_byref_3中的layout就是__block修饰的变量。
2,判断是否有copy和release函数。没有函数其实就是基本数据类型的变量,copy结构体中size变量后面的数据到新的Block_byref中去,src+1即size后面的数据。有则先复制其copy和release函数,如果有变量值则再将值赋给新的block,然后调用copy函数。copy函数就是__Block_byref_id_object_copy_131。其内部又调用了_Block_object_assign,来对是持有的变量进行copy。变量的位置就是40个字节的位置,flags和size共占8个字节。在_Block_object_assign内部,flags是131则又做了一次赋值。此处有一个问题源码中是直接赋值
*dest = object;
这种引用计数不会变化,也就是没有强制引用,但从实际来看,__block修饰的变量,在block内部确实是强制引用了的,
_Block_byref_copy最后返回了src->forwarding,即copy后的byref。
大致流程图
对流程的一些说明,
1.desc中copy是否存在是根据block中引用的外部变量的类型决定的,基本数据类型是没有copy的。
2.调用_Block_object_assign函数时,变量类型是事先指定好的。
3.byref中copy是否存在是根据__block修饰的变量类型决定的,基本数据类型是没有copy的。
_Block_use_RR2函数用来保存retain、release和destructInstance函数,前2个处理block持有的对象,后1个在block销毁时处理block自身,调用时机是在libSystem进行初始化时,libSystem_initializer调用libdispatch_init,然后_os_object_init,再调用的_Block_use_RR2。
_os_object_init中调用如下
Block_callbacks_RR callbacks = {
sizeof(Block_callbacks_RR),
(void (*)(const void *))&objc_retain,
(void (*)(const void *))&objc_release,
(void (*)(const void *))&_os_objc_destructInstance
};
_Block_use_RR2(&callbacks);
static void*
_os_objc_destructInstance(id obj)
{
// noop if only Libystem is loaded
return obj;
}
_Block_use_RR2中保存了函数指针。
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
_Block_retain_object = callbacks->retain;
_Block_release_object = callbacks->release;
_Block_destructInstance = callbacks->destructInstance;
}
根据源码来看_Block_destructInstance保存的是_os_objc_destructInstance,但实际中_Block_destructInstance却指向了libobjc中的objc_destructInstance,这是怎么回事?原来_Block_use_RR2又被调用了,这次是CoreFoundation的__CFInitialize中调用了CFMakeNSBlockClasses,这里面初始化了各种Block类型,除了前面说的3种,还有NSAutoBlock、NSFinalizingBlock等。然后调用了_Block_use_RR2,将_Block_destructInstance指向了libobjc中的objc_destructInstance,不过这部分代码没有开源。
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
free(aBlock);
}
}
_Block_release里面对Global类型的直接返回,只处理MallocBlock,先将引用计数减2(为了方便位运算),若没有引用则进行销毁。先调用block内部的release,处理持有的变量。再处理block自身。最后释放掉。
4 Block的签名
签名其实就是字符串,描述了Block的参数和返回值。类似method_getTypeEncoding函数。
int(^block)(id obj) = (id)^(id obj){
NSLog(@"obj:%@", obj);
return 10;
};
签名就是"i16@?0@8",i代表返回值类型int,@?表示block自身,最后的@代表参数类型。
如果引用了变量,变量存放在desc指针的后面,即block+32字节的位置。如果是__block修饰的。这个位置存放的是bryef类型的指针,具体的对象在偏移20的字节的位置
关于Block内部的强引用问题,见block中 使用 __weak和__strong
来源
1 https://opensource.apple.com/source/objc4/objc4-706/
2 https://opensource.apple.com/source/libclosure/libclosure-67/