理解该部分则需要明白Block原理,该部分探究辅助拷贝\销毁函数作用。本文从拷贝函数入手
什么情况下Block从栈拷贝到堆中
在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上。
1.手动调用copy
2.Block是函数的返回值
3.Block被强引用,Block被赋值给__strong或者id类型
4.调用系统API入参中含有usingBlcok的方法
以上4种情况,系统都会默认调用copy方法把Block赋复制
注意:(oc中对象默认所有权为__strong),所以把Block赋值给对象则执行copy
为什么需要辅助考本函数
Block引用栈上对象,栈生命周期完成则该对象释放。而Block引用已经释放外部对象则会崩溃,为此Block会把该对象拷贝一份放置到堆中,同时block内部引用该堆中的对象,而辅助拷贝函数则:
其一:拷贝原有对象内容至堆上,
其二:管理原有对象向引用计数
辅助拷贝函数出现时机
Block由于内部引用外部对象,因此就会出现辅助拷贝函数,辅助拷贝函数作用是把引用的对象复制到,
int main(int argc, const char * argv[]) {
__block id block_obj = [[NSObject alloc]init];
id obj = [[NSObject alloc] init];
NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
void (^myBlock)(void) = ^{
NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
};
myBlock();
return 0;
}
执行clang -rewrite-objc main.m
获取到cpp文件
struct __Block_byref_block_obj_0 {
void *__isa;
__Block_byref_block_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id block_obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id obj;
__Block_byref_block_obj_0 *block_obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__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_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
id obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders__h_7gr165cx2qz0nfs1bnd1t5kc0000gp_T_main_71363e_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
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(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders__h_7gr165cx2qz0nfs1bnd1t5kc0000gp_T_main_71363e_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj);
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
其中栈Block拷贝到堆上过程如下:
/* Copy, or bump refcount, of a block. If really copying, call the copy helper if present. */
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
//printf("_Block_copy_internal(%p, %x)\n", arg, flags);
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
//如果block的flags字段包含BLOCK_NEEDS_FREE,说明是堆Block
//那么这是一个堆block(稍后你就明白)。
//这里只需要增加引用计数然后返回原blcok。
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
//BLOCK_IS_GC,MAC开发
else if (aBlock->flags & BLOCK_IS_GC) {
// GC refcounting is expensive so do most refcounting here.
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;
}
//如果这是一个全局Block,则b直接返回原Block
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// Its a stack block. Make a copy.
//是一个栈Block 需要copy
if (!isGC) {
//在堆上申请内存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
//将当前栈上分配的block按位拷贝到我们刚刚创建的堆内存上
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
//清楚引用计数
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
//设置了BLOCK_NEEDS_FREE标志位,表明这是一个堆block
//并设置引用计数为 1
result->flags |= BLOCK_NEEDS_FREE | 1;
//block的isa指针被设置为_NSConcreteMallocBlock,说明这是个堆block
result->isa = _NSConcreteMallocBlock;
//如果block有一个拷贝辅助函数,那么它将被调用
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;
}
else {
// Under GC want allocation with refcount 1 so we ask for "true" if wantsOne
// This allows the copy helper routines to make non-refcounted block copies under GC
unsigned long int flags = aBlock->flags;
bool hasCTOR = (flags & BLOCK_HAS_CTOR) != 0;
//在堆上申请内存
struct Block_layout *result = _Block_allocator(aBlock->descriptor->size, wantsOne, hasCTOR);
if (!result) return (void *)0;
//将当前栈上分配的block按位拷贝到我们刚刚创建的堆内存上
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
// if we copy a malloc block to a GC block then we need to clear NEEDS_FREE.
flags &= ~(BLOCK_NEEDS_FREE|BLOCK_REFCOUNT_MASK); // XXX not needed
if (wantsOne)
flags |= BLOCK_IS_GC | 1;
else
flags |= BLOCK_IS_GC;
result->flags = flags;
if (flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper...\n");
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
if (hasCTOR) {
result->isa = _NSConcreteFinalizingBlock;
}
else {
result->isa = _NSConcreteAutoBlock;
}
return result;
}
}
整个拷贝过程如下:
1 arg参数(传进来的Block)出错则返回
2 不同Blcok类型,不同处理方式
2.1 如果拷贝的是堆Block则堆Block引用计数自加1
2.2 如果是MAC上开发的Blcok则处理
2.3 如果这是一个全局Block,则b直接返回原Block
3 拷贝过程
3.1 堆上申请内存
3.2 将当前栈上分配的block按位拷贝到我们刚刚创建的堆内存上
3.3 清楚该Block的引用计数
3.4 设置了BLOCK_NEEDS_FREE标志位,表明这是一个堆block,并设置引用计数为1
3.5 如果block有一个拷贝辅助函数,那么它将被调用
辅助拷贝函数执行内容
辅助拷贝函数入参Block分别是:堆上dst的Block和原src的Block
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
从辅助拷贝函数可以看出主要调用了_Block_object_assign方法,而该方法内部执行
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
// (this test must be before next one)
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
// copying a Block declared variable from the stack Block to the heap
//拷贝一个Block变量 从栈上到堆上
_Block_assign(_Block_copy_internal(object, flags), destAddr);
}
// (this test must be after previous one)
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
其中针对捕获不同对象类型,执行不同的操作:
BLOCK_FIELD_IS_BYREF类型(8):
拷贝对象被__block修饰的,执行_Block_byref_assign_copy方法,该方法把__block的对象内部也复制到堆上
BLOCK_FIELD_IS_OBJECT类型(3):
如果捕获到的是对象,则原有对象引用计数自加1
BLOCK_FIELD_IS_BLOCK类型(7):
如果捕获到的是Blcok
BLOCK_FIELD_IS_WEAK类型(16):
如果捕获到的是被__weak修饰的变量
可以看出内部有3个判断, 分别调用了 _Block_byref_assign_copy 、 _Block_assign、_Block_retain_object
捕获__Block修饰的对象执行_Block_byref_assign_copy
// 运行时入口点,用于维护分支数据块的共享知识. 复制闭包后, 修改引用var时共享同步byref数据, byref 指针已经复制到堆上, 栈上修改堆上需要同步
// var byref指针是否已经被复制到堆中,如果是的话,之后在用var直接retain引用计数。
// 否则,我们需要复制它, 并更新栈forwarding指针指向
// 参数
/// dest: var地址
/// arg: var对象
/// flags: var类型
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
// 可以看到初始化时将flags赋值为0 代表初次copy var
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 是否是weak(只有在GC) 所以可以忽略 === fasle
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
/// 如果它的弱请求一个对象(仅在GC下重要)
// 堆中申请一个src大小的内存并且用byref类型指针指向, 其实就是复制var到堆中
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
// 下面就是为copy出来的byref 指针赋值而已
// 非GC用于调用者,一个用于栈, 植入BLOCK_NEEDS_FREE, 保证后期在次引用不会再copy
copy->flags = src->flags | _Byref_flag_initial_value;
#/// 重要把栈上得forwarding指针指向堆上,而堆上的forwarding也指向自己
copy->forwarding = copy; // patch 拷贝堆中指针 指向自己
src->forwarding = copy; // patch 栈指向堆拷贝
copy->size = src->size;
// 如果是weak(只有在GC) 所以不会进
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; // 标记isa字段,因此它会弱扫描
}
// BLOCK_HAS_COPY_DISPOSE 这个byref var具有自己的copy/dispose辅助函数,而此时我们的内部实现不会进行默认的复制操作
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
else {
// just bits. Blast 'em using _Block_memmove in case they're __strong
// 只是bits。Blast'em使用_Block_memmove,以防它们是__strong
_Block_memmove(
(void *)©->byref_keep,
(void *)&src->byref_keep,
src->size - sizeof(struct Block_byref_header));
}
}
// already copied to heap
// 已复制到堆之后在调用
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
// 当再次拷贝i时,则仅仅retain其引用计数
latching_incr_int(&src->forwarding->flags);
}
// assign byref data block pointer into new Block
//将byref数据块指针分配给新的Block
_Block_assign(src->forwarding, (void **)destp); // 这句仅仅是直接赋值,其函数实现只有一行赋值语句,查阅runtime.c可知
}
捕获的object是一个objective-c 对象
// 如果在MRC下内部会对其retain
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void _Block_retain_object_default(const void *ptr) {
if (!ptr) return;
}
捕获的object是一个block
static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;
static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}
网友评论