Block之捕获OC类型的对象
该文章的分析都是基于ARC下的。
Block分析是基于以下源码:
int main(int argc, const char * argv[]) {
NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
void (^g_block)(void);
{
MyObject *obj = [[MyObject alloc] init];
g_block = ^{
[obj i_method1];
};
}
g_block();
g_block = nil;
[currentLoop run];
return 0;
}
通过clang -rewrite-objc main.mm
可以将OC代码生成可读的C源码。如下,这里只贴出主要部分的代码而且做了简化操作,详细信息自己可以生成看下。
备注:其实目前生成的C源码和实际运行的是不一样的。详见下文。
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;
MyObject *obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
MyObject *obj = __cself->obj; // bound by copy
[obj i_method1];
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
//ARC下是不会调用该方法的,其实调用的是objc_storeStrong(&dst->obj, src->obj);
_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)
{
//ARC下是不会调用该方法的,其实调用的是objc_storeStrong(&src->obj, nil);
_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[]) {
void (*g_block)(void);
{
MyObject *obj = [[MyObject alloc] init];
g_block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
}
((void (*)(__block_impl *))((__block_impl *)g_block)->FuncPtr)((__block_impl *)g_block);
g_block = __null;
return 0;
}
这里可以看出block上是个结构体,而且还生成了一个静态全局对象:__main_block_desc_0_DATA
,该对象提供了一个copy和dispose方法,这两个方法的说明请往下看。
下面重点分析main函数中的代码。
__main_block_impl_0 构造函数
当函数执行到g_block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344);
时,开始执行__main_block_impl_0
的构造方法:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
注意这里会执行一段obj(_obj)函数,这里其实就等于obj=_obj
,在执行这段代码的时候强大的编译器来了,会将赋值操作分解成下面两个函数:
这时这个MyObject对象相当于被block强引用一次。(注:目前该block是在栈上的)
然后继续执行该构造方法中的代码,这时会执行impl.isa = &_NSConcreteStackBlock;
,这表示该block是在栈上创建的。
impl.Flags = flags;(BLOCK_HAS_COPY_DISPOSE| BLOCK_USE_STRET)
这里的flags是如下类型:
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
};
执行完该构造函数后g_block
已经创建好了而且是在栈区,这时强大的编译器又出现了,会在执行完构造函数后面又会生成block copy的代码:g_blcok = _Block_copy(g_block);
下面看下_Block_copy的执行逻辑。
_Block_copy
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
if (!arg) return NULL;
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size);
result->flags &= ~(BLOCK_REFCOUNT_MASK);
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock);
}
return result;
}
这里最后一个函数(*aBlock->descriptor->copy)(result, aBlock);
就会用到__main_block_desc_0_DATA
中的copy方法了。
执行完g_blcok = _Block_copy(g_block);
后,g_block已经从栈上移到了堆上了。而且g_block捕获的oc对象也会被再次retain下。至于g_block捕获的oc对象是如何被retain的,请看下面的__main_block_desc_0_DATA
中的copy方法。
__main_block_desc_0_DATA
在上面生成的C源码中__main_block_desc_0_DATA
的copy和dispose方法分别是:
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3);
_Block_object_dispose((void*)src->obj, 3);
这两个函数最后面的那个参数是如下类型:
enum {
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.
};
勘误:
在ARC下执行(*aBlock->descriptor->copy)(result, aBlock);时实际上调用的并不是 _Block_object_assign而是objc_storeStrong方法。同样调用(*aBlock->descriptor->dispose)(aBlock)时也不会调用_Block_object_dispose而是objc_storeStrong。
以上两个方法都是在MRC下调用的。
所以可以将上面两个方法替换为:
objc_storeStrong(&dst->obj, src->obj);
objc_storeStrong(&src->obj, nil);
objc_storeStrong
下面看下objc_storeStrong
的源码实现:
void objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
objc_storeStrong
就是将obj对象retain,然后赋值给*location
,并将release掉prev。
release方法
g_blcok = _Block_copy(g_block);
执行完这个函数后,就会调用g_block();
方法执行block中的函数,最后调用g_block = nil;
方法销毁block,当执行到该段代码时强大的编译器又出现了,它会插入如下代码来销毁block。如下图:
下面看下_Block_release源码实现:
void _Block_release(void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
int32_t newCount;
if (!aBlock) return;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
if (newCount > 0) return;
// Hit zero
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
else {
printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
}
}
这时就会调用__main_block_desc_0_DATA
中的dispose
方法。最终会执行到objc_storeStrong(&src->obj, nil);
该方法。至此g_block解引用了它捕获到的全部对象,g_block对象也被销毁掉了。
备注:
latching_incr_int
表示将block的引用计数加1。latching_decr_int
表示将block的引用计数减1,并返回retainCount值。
参考
- BlocksRuntime
- Block_private.h
- Block Implementation
- Blocks的实现
- OC Runtime源码