该文章的分析都是基于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,在执行这段代码的时候强大的编译器来了,会将赋值操作分解成下面两个函数:

block_01_01

这时这个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_01_02

下面看下_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对象也被销毁掉了。

备注:

  1. latching_incr_int表示将block的引用计数加1。
  2. latching_decr_int 表示将block的引用计数减1,并返回retainCount值。

参考

  1. BlocksRuntime
  2. Block_private.h
  3. Block Implementation
  4. Blocks的实现
  5. OC Runtime源码