史上最详细的Block源码剖析

前言

  之前写过一篇block的文章,参考的源码是libclosure-38的,跟libclosure-67有所区别,且由于之前理解不足文章有些细小错误,决定重新写一篇。
  关于Block的文章各个博客各个论坛都已经数不胜数,每一篇都有自己独特的特点和见解。但很多文章深度不够,只是简单的把一些源码放上去,并未分析每一步操作的作用,比较不友好,只能看得一知半解。笔者刚看的时候也是一头雾水,花费了很长时间查阅了各种资料才渐渐理解。鉴于此,决定写一篇事无巨细的文章来剖析Block的每一个步骤,来帮助大家理解Block的奥秘。
  Block的使用这里就不再做过多解释,下面会通过分析源码来讲解Block的实现、变量的捕获以及__block的作用等。

Block

Block类型

  看一段代码,以下在ARC下执行。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       int num = 1;
       static int staticNum = 2;
       char str[] = "hello";
       void(^aBlock)(void) = ^(void) {
           printf("%d\n", num);
       };
       void(^bBlock)(void) = ^(void) {
          printf("%d\n", staticNum);
       };
       void(^cBlock)(char *) = ^(char *word) {
           printf("%s\n", word);
       };
       aBlock();
       bBlock();
       cBlock(str);
       NSLog(@"%@----%@----%@----%@", aBlock, bBlock, cBlock, ^{NSLog(@"%d", num);});
    }
    return 0;
}

打印结果如下:

<__NSMallocBlock__: 0x100607950>----<__NSGlobalBlock__: 0x100002118>--
--<__NSGlobalBlock__: 0x100002158>----<__NSStackBlock__: 0x7fff5fbff5c0>

  其中因为bBlock和cBlock只使用了静态变量和入参,不需要捕获外部变量,所以为全局Block __NSGlobalBlock__,存在于全局区,内存在程序结束后由系统回收。最后一个使用了外部变量num,为栈Block__NSStackBlock__,内存由编译器控制,过了作用域就会被回收。而aBlock虽然也只使用了外部变量,但由于在ARC下会自动调用一次copy方法,将Block从栈区copy到堆区,所以aBlock为堆Block__NSMallocBlock__,内存由ARC控制,没有强指针指向时释放。而在MRC中,赋值不会执行copy操作,所以aBlock依然会存在于栈中,所以在MRC中一般都需要执行copy,否则很容易造成crash。
  在ARC中,当Block作为属性被strong、copy修饰或被強指针应用或作为返回值时,都会默认执行copy方法。而MRC中,只有被copy修饰时,Block才会执行copy。所以MRC中Block都需要用copy来修饰,而在ARC中用copy修饰只是沿用了MRC的习惯,此时用copy和strong效果是相同的。下面会讲到copy的具体实现。

Block数据结构定义

  Block的定义在Block_private.h中,点击查看源码

// 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 
};

#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
    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
};

  用一张被用烂的图,点击查看原文

结构图

  事实上该图适用于libclosure-38,在最新的源码中结构已经有所改变,但整体并未改变,只是在基础上新增了signaturelayout,依然可以用其来理解。

  • isa指针
    指向父类的结构体,就是_NSConcreteStackBlock_NSConcreteMallocBlock_NSConcreteGlobalBlock这几个,说明OC本身也是一个对象。
  • flags
    就是上面那几个枚举,用来保留block的一些信息,用到的如下:
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime  
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 用来标识栈Block
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime  用来标识堆Block
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler compiler 含有copy_dispose助手
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler 是否为全局Block
};

BLOCK_REFCOUNT_MASK、BLOCK_IS_GLOBAL和BLOCK_NEEDS_FREE用来标识Block类型,BLOCK_HAS_COPY_DISPOSE判断Block是否有copy_dispose助手,即description2中的copy和dispose函数,用来管理捕获对象的内存,下面会细讲。

  • reserved
    保留信息
  • invoke
    函数指针,指向block具体的执行函数
  • description
    block附加描述信息,主要保存了内存size以及copy和dispose函数的指针及签名和layout等信息,通过源码可发现,layout中只包含了Block_descriptor_1,并未包含Block_descriptor_2和Block_descriptor_3,这是因为在捕获不同类型变量或者没用到外部变量时,编译器会改变结构体的结构,按需添加Block_descriptor_2和Block_descriptor_3,所以才需要BLOCK_HAS_COPY_DISPOSE和BLOCK_HAS_SIGNATURE等枚举来判断
  • variables capture的外部变量,如果Block中使用了外部变量,结构体中就会有相应的信息,下面会解释。Block将使用的变量或者变量指针copy过来,内部才可以访问。

Block_byref的结构定义

  上面介绍了Block也就是Block_layout的源码,Block_private.h中还有定义了一个结构体Block_byref,变量在被__block修饰时由编译器来生成,结构如下:

enum {
    // Byref refcount must use the same bits as Block_layout's refcount.
    // BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime

    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler

    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime

    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime
};

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_layout结构类似

  • isa指针
    指向父类,一般直接指向0,后面会说。
  • forwarding指针
    在栈中指向自己,Block执行copy后指向堆中的byref。
  • flags
    runtime中用到的如下:
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime

BLOCK_BYREF_LAYOUT_EXTENDED表示含有layout,BLOCK_BYREF_HAS_COPY_DISPOSE表示byref中含有copy_dispose函数,在__block捕获的变量为对象时就会生成copy_dispose函数用来管理对象内存,BLOCK_BYREF_NEEDS_FREE判断是否要释放。

  • size
    所占内存大小
  • Block_byref_2、Block_byref_3
    Block_byref_2和Block_byref_3用来保存附加信息

_NSConcreteGlobalBlock的实现

  使用Clang可以将OC转成C++,就可以看到Block具体做了什么。clang -rewrite-objc filename,比如我需要转换main.m中的代码就可以写clang -rewrite-objc main.m,注意路径正确。转换后的.cpp文件有10万行,只需要看最后几行。
  上面说到,在block内部不使用外部变量或者为静态或者全局变量时,该block就是_NSConcreteGlobalBlock,看下编译后的结果。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        ^{
            printf("hello block");
        }();
        
    }
    return 0;
}

Clang后

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 block");
        }

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 argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();

    }
    return 0;
}

  从上可以看到,__block_impl__main_block_impl_0以及__main_block_desc_0这三个结构体一起组成了整个Block,生成的结构体各个参数跟上面介绍的一致。编译器将Block内的函数定义成一个独立的函数__main_block_func_0,在最后调用。
  不过有两点问题:

  • 1、该Block并未使用外部变量,应该是一个_NSConcreteGlobalBlock,而用clang显示的却是_NSConcreteStackBlock,唐巧的说法是:clang 改写的具体实现方式和 LLVM 不太一样。这点不是很清楚,如果有了解的可以释疑一下。
  • 2、把编译后的代码copy到mm中后,((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();此行会crash,因为((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))指向的是impl的isa,也就是_NSConcreteStackBlock的指针。需要改写成struct __main_block_impl_0 impl_0 = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA); ((void (*)())(impl_0.impl.FuncPtr))();才能执行成功。或许clang改写的实现跟LLVM确实不一样吧。

_NSConcreteStackBlock的实现

  如果block内部使用了普通的外部变量,就是_NSConcreteStackBlock

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        int a = 1;
        NSObject *obj = [[NSObject alloc] init];
        ^{
            obj, a;
        };
    }
    return 0;
}

Clang后

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;
  NSObject *obj;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int _a, int flags=0) : obj(_obj), a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *obj = __cself->obj; // bound by copy
  int a = __cself->a; // bound by copy

            obj, a;
        }
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, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 1;
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, a, 570425344));
    }
    return 0;
}

  该例在Block中同时使用了普通变量和实例变量,Block在捕获外部变量的操作基本一致,都是在生成结构体的时候将所有Block里用到的外部变量作为属性存起来。这就是为什么在self.block里调用self会造成循环引用,因为Block捕获了self并把self当做一个值保存了起来。
  而且如果此时在Block里面改变a的值是会报错,因为普通变量的值传递为复制,所有Block里的a只是copy过去的a的值,在Block里改变a的值也不会影响外面,编译器避免这个错误就报错。同样的,捕获对象时也是对指针的copy,生成一个指针指向obj对象,所以如果在Block中直接让obj指针指向另外一个对象也会报错。这点编译器已经加了注释// bound by copy。
  同时,该例与上例有所区别,多了__main_block_copy_0__main_block_dispose_0这两个函数,并在描述__main_block_desc_0结构体中保存了这两个函数指针。这就是上面所说的copy_dispose助手,C语言结构体中,编译器很难处理对象的初始化和销毁操作,所以使用runtime来管理相关内存。BLOCK_FIELD_IS_OBJECT是在捕获对象时添加的特别标识,此时是3,下面会细讲。
  此Block是为栈Block_NSConcreteStackBlock,不过在ARC中,因为赋值给aBlock,会执行一次copy,将其中栈中copy到堆中,所以在MRC中aBlock为_NSConcreteStackBlock,在ARC中就是_NSConcreteMallocBlock

_NSConcreteMallocBlock && Block_copy()

  上面说到,ARC中生成的_NSConcreteStackBlock在赋值给强指针时会默认执行一次copy变成_NSConcreteMallocBlock,下面介绍下copy的实现。
  在Block.h中定义了Block_copy的宏

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))

  其实现在runtime.h

void *_Block_copy(const void *arg) {
    //1、
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    //2、
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    //3、
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    //4、
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        //5、
        // Its a stack block.  Make a copy.
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        //6、
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        //7、
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        //8、
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        //9、
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

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
}

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;
}

  以上就是_NSConcreteStackBlock转换成_NSConcreteMallocBlock的过程,下面按步解释每一步的作用。

  • 1、先声明一个Block_layout结构体类型的指针,如果传入的Block为NULL就直接返回。
  • 2、如果Block有值就强转成Block_layout的指针类型。
  • 3、如果Block的flags表明该Block为堆Block时,就对其引用计数递增,然后返回原Block。latching_incr_int这个函数进行了一次死循环,如果flags含有BLOCK_REFCOUNT_MASK证明其引用计数达到最大,直接返回,需要三万多个指针指向,正常情况下不会出现。随后做一次原子性判断其值当前是否被其他线程改动,如果被改动就进入下一次循环直到改动结束后赋值。OSAtomicCompareAndSwapInt的作用就是在where取值与old_value相同时,将old_value+2赋给where。 注:Block的引用计数以flags的后16位代表,以 2为单位,每次递增2,1被BLOCK_DEALLOCATING正在释放占用。
  • 4、如果Block为全局Block就不做其他处理直接返回。
  • 5、该else中就是栈Block了,按原Block的内存大小分配一块相同大小的内存,如果失败就返回NULL。
  • 6、memmove()用于复制位元,将aBlock的所有信息copy到result的位置上。
  • 7、将新Block的引用计数置零。BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING就是0xffff~(0xffff)就是0x0000result->flags0x0000与等就将result->flags的后16位置零。然后将新Block标识为堆Block并将其引用计数置为2。
  • 8、如果有copy_dispose助手,就执行Block的保存的copy函数,就是上面的__main_block_copy_0。在_Block_descriptor_2函数中,用BLOCK_HAS_COPY_DISPOSE来判断是否有Block_descriptor_2,且取Block的Block_descriptor_2时,因为有无是编译器确定的,在Block结构体中并无保留,所以需要使用指针相加的方式来确定其指针位置。有就执行,没有就return掉。
  • 9、将堆Block的isa指针置为_NSConcreteMallocBlock,返回新Block,end。
      因为在ARC中的赋值的自动copy,所以:
        NSObject *obj = [[NSObject alloc] init];
        void(^aBlock)() = ^{
            obj;
        };
        NSLog(@"%ld", CFGetRetainCount((__bridge void *)obj));

  其打印结果为3,因为Block在生成栈Block捕获外部变量的时候会生成一份指针指向obj,在将Block赋值给aBlock时从栈中copy到堆区中,又持有了一份。

Block_release()

  有Block_copy()就有对应的Block_release(),其在Block.h中的定义如下:

#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

  其实现在runtime.h

void _Block_release(const void *arg) {
    //1、
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    //2、
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    //3、
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
    //4、
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        //5、
        _Block_call_dispose_helper(aBlock);
        //6、
        _Block_destructInstance(aBlock);
        //7、
        free(aBlock);
    }
}

static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return false; // latched high
        }
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
            return false;   // underflow, latch low
        }
        int32_t new_value = old_value - 2;
        bool result = false;
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
            new_value = old_value - 1;
            result = true;
        }
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
            return result;
        }
    }
}

static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->dispose)(aBlock);
}

static void _Block_destructInstance_default(const void *aBlock __unused) {}
static void (*_Block_destructInstance) (const void *aBlock) = _Block_destructInstance_default;

  • 1、将入参arg强转成(struct Block_layout *)类型,如果入参为NULL则直接返回。
  • 2、如果入参为全局Block则返回不做处理。
  • 3、如果入参不为堆Block则返回不做处理。
  • 4、判断aBlock的引用计数是否需要释放内存。与copy同样的,latching_decr_int_should_deallocate也做了一次循环和原子性判断保证原子性。如果该block的引用计数过高(0xfffe)或者过低(0)返回false不做处理。如果其引用计数为2,则将其引用计数-1即BLOCK_DEALLOCATING标明正在释放,返回true,如果大于2则将其引用计数-2并返回false。
  • 5、如果步骤4标明了该block需要被释放,就进入步骤5。如果aBlock含有copy_dispose助手就执行aBlock中的dispose函数,与copy中的对应不再多做解释。
  • 6、_Block_destructInstance默认没做其他操作,可见源码。
  • 7、最后一步释放掉aBlock,end。

__block的作用及实现

  前面说到,Block捕获的变量不允许在内部改变指针指向,因为内部的对象是重新copy的一份指针或者普通变量值。如果需要改变普通变量的值或指针的指向,这个时候就需要用到__block。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        __block int a = 1;
        __block NSObject *obj = [[NSObject alloc] init];
        ^{
            a++;
            obj;
        }();
    }
    return 0;
}

Clang后

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);
}

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __Block_byref_obj_1 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_obj_1 *_obj, int flags=0) : a(_a->__forwarding), obj(_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_a_0 *a = __cself->a; // bound by ref
  __Block_byref_obj_1 *obj = __cself->obj; // bound by ref

            (a->__forwarding->a)++;
            (obj->__forwarding->obj);
        }
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*/);_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->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
        __attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __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"))};
        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_obj_1 *)&obj, 570425344))();
    }
    return 0;
}

  可以看到加了__block的a和obj已经被编译成了两个结构体__Block_byref_a_0__Block_byref_obj_1,这两个就是上面所说的Block_byref。可以看到起结构大同小异,都有__isa指针指向0,__forwarding指针指向自己,__flags用来标记,__size用来保存内存大小,a和obj指向最初的变量。可以发现,对象的结构体多了两个方法用来处理obj的内存。注释也变成了// bound by ref,这时候就可以修改变量a和对指针obj重新赋值。对象结构体比普通变量多了两个函数,用来处理NSOject的内存。
  上面说Block捕获的对象会持有该对象,使其引用计数增加,然后看下__block修饰的对象会怎样。

    __block NSObject *obj = [[NSObject alloc] init];
    NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
    ^{
        NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
    }();
    NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);

打印结果如下:

1---0x60c000007d50---0x7fff5e913c28
1---0x60c000007d50---0x7fff5e913c28
1---0x60c000007d50---0x7fff5e913c28

  可以看到,在栈Block中,自始只有强指针指向obj,就是__block生成的结构体。
  然后看下在copy到堆中的效果:

__block NSObject *obj = [[NSObject alloc] init];
    NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
    void(^aBlock)() = ^{
        NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
    };
    aBlock();
    NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);

结果如下:

1---0x604000012ca0---0x7fff50631c28
1---0x604000012ca0---0x600000058c28
1---0x604000012ca0---0x600000058c28

  可以看到,obj的内存地址一直在栈中,而执行了BlockCopy后,obj指针的地址从栈变到了堆中,而obj的引用计数一直是1。在copy操作之后,结构体obj也被复制到了堆中,并断开栈中的obj结构体对obj对象的指向。那如果这个时候取栈中的obj不就有问题了?__forwarding就派上用场了,上面编译的结果发现,结构体对象在使用obj的时候会使用obj->__forwarding->obj,如果所有__forwarding都指向自己,这一步还有什么意义?栈Block在执行copy操作后,栈obj结构体的__forwarding就会指向copy到堆中的obj结构体。此时再取值,操作的就是同一份指针了。证明如下:

        __block NSObject *obj = [[NSObject alloc] init];
        NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
        void(^aBlock)() = ^{
            NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
        };
        aBlock();
        void(^bBlock)() = [aBlock copy];
        bBlock();
        aBlock();
        NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);

注:该段在MRC下执行,否则栈Block赋值给aBlock就会默认执行copy操作变成堆block。
打印结果:

1---0x1006229d0---0x7fff5fbff6b8
1---0x1006229d0---0x7fff5fbff6b8
1---0x1006229d0---0x100508c98
1---0x1006229d0---0x100508c98
1---0x1006229d0---0x100508c98

  由上可知,在copy之前,aBlock的打印结果都是初始化生成的指针,而copy之后打印就和bBlock的打印结果相同了。总结一下就是,在栈中的obj结构体__forwarding指向的就是栈中的自己,执行copy之后,会在堆中生成一份obj结构体并断开栈中对obj的引用,此时堆中的obj结构体__forwarding就指向自己,而栈中的__forwarding就指向堆中的obj结构体。下面也会通过分析源码来具体解释。

_Block_object_assign

  虽然上面已经讲了很多,但细心的同学可以发现,在Clang后的代码中还有两个没有讲到,就是编译器生成的copy_dispose代码中调用的两个用来管理对象内存的函数_Block_object_assign_Block_object_dispose
  然后先讲_Block_object_assign。大家可以在Block.h中看到:

BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

  其实现在runtime.c中,其中用到很多枚举,如果不懂其含义,很难理解其上面的代码,所以在此之前先解释下各种枚举所代表的意思。
  其枚举定义在Block_private.h中:

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    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.
};

enum {
    BLOCK_ALL_COPY_DISPOSE_FLAGS = 
        BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
        BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
};
  • BLOCK_FIELD_IS_OBJECTOC对象类型;
  • BLOCK_FIELD_IS_BLOCK为另一个block;
  • BLOCK_FIELD_IS_BYREF为一个被__block修饰后生成的结构体;
  • BLOCK_FIELD_IS_WEAK被__weak修饰过的弱引用,只在Block_byref管理内部对象内存时使用,也就是__block __weak id
  • BLOCK_BYREF_CALLER在处理Block_byref内部对象内存的时候会加的一个额外标记,配合上面的枚举一起使用;
  • BLOCK_ALL_COPY_DISPOSE_FLAGS上述情况的整合,即以上都会包含copy_dispose助手。
      现在再看_Block_object_assign:
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];
        ********/
        //1.
        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
        //2.
        *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];
         ********/
        //3.
        *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];
         ********/
        //4.
        *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];
         ********/
        //5.
        *dest = object;
        break;

      default:
        break;
    }
}
static void _Block_retain_object_default(const void *ptr __unused) { }
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;

static struct Block_byref *_Block_byref_copy(const void *arg) {
    //3.1
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        //3.2
        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
        //3.3
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        //3.4
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        //3.5
        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
            //3.6
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            //3.7
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                //3.8
                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;
            }
            //3.9
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            //3.10
            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) {
        //3.11
        latching_incr_int(&src->forwarding->flags);
    }
    //3.12
    return src->forwarding;
}

  destArg为执行Block_copy()后的block中的对象、block、或者BYREF指针的指针,obj为copy之前的变量指针。

  • 1、当block捕获的变量为OC对象时执行此步,ARC中引用计数由强指针来确定,所以_Block_retain_object默认是不做任何操作,只进行简单的指针赋值。
  • 2、当block捕获的变量为另外一个block时执行此步,copy一个新的block并赋值。
  • 3、当block捕获的变量为__block修饰的变量时会执行此步,执行byref_copy操作。
    3.1、强转入参为(struct Block_byref *)类型。
    3.2、当入参为栈byref时执行此步。分配一份与当前byref相同的内存,并将isa指针置为NULL。
    3.3、将新byref的引用计数置为4并标记为堆,一份为调用方、一份为栈持有,所以引用计数为4。
    3.4、然后将当前byref和malloc的byref的forwading都指向堆byref,然后操作堆栈都是同一份东西。
    3.5、最后将size赋值
    3.6、如果byref含有需要内存管理的变量即有copy_dispose助手,执行此步。分别取出src的Block_byref_2和copy的Block_byref_2
    3.7、将src的管理内存函数指针赋值给copy。
    3.8、如果src含有Block_byref_3,则将src的Block_byref_3赋值给copy。
    3.9、执行byref的byref_keep函数(即_Block_object_assign,不过会加上BLOCK_BYREF_CALLER标记),管理捕获的对象内存。
    3.10、如果捕获的是普通变量,就没有Block_byref_2,copy+1和src+1指向的就是Block_byref_3,执行字节拷贝。
    3.11、如果该byref是存在于堆,则只需要增加其引用计数。
    3.12、返回forwarding。
  • 4、如果管理的是__block修饰的对象或者block的内存会执行此步,直接进行指针赋值。
  • 5、同时被__weak和__block修饰的对象或者block执行此步,也是直接进行指针赋值。

_Block_object_dispose

  与_Block_object_assign对应的。

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        //1.
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        //2.
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        //3.
        _Block_release_object(object);
        break;
      //4.
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

static void _Block_byref_release(const void *arg) {
    //1.1
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        //1.2
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);

        if (latching_decr_int_should_deallocate(&byref->flags)) {
            //1.3
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                //1.4
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            //1.5
            free(byref);
        }
    }
}

static void _Block_release_object_default(const void *ptr __unused) { }
static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;
  • 1、如果需要管理的变量为byref,则执行该步。
    1.1、将入参强转成(struct Block_byref *),并将入参替换成forwarding。
    1.2、如果该byref在堆上执行此步,如果该byref还被标记为栈则执行断言。
    1.3、此函数上面有讲就不多提,判断是否需要释放内存。
    1.4、如果有copy_dispose助手就执行byref_destroy管理捕获的变量内存。
    1.5、释放byref。
  • 2、如果block则调用_Block_release释放block,上面有讲。
  • 3、如果是OC对象就进行release,默认没有做操作,由ARC管理。
  • 4、如果是其他就不做处理,__block修饰的变量只有一个强指针引用。

测试

  如果这么长都看完了,想必你已经对block有了深刻的认识,来波测试吧(本文只有此测试是抄的)。下面的选项均为:A、ARC可以正常执行。B、ARC和MRC均可以正常执行。C、均不可正常执行。

Example A
void exampleA() {
  char a = 'A';
  ^{
    printf("%c\n", a);
  }();
}
Example B
void exampleB_addBlockToArray(NSMutableArray *array) {
  char b = 'B';
  [array addObject:^{
    printf("%c\n", b);
  }];
}

void exampleB() {
  NSMutableArray*array = [NSMutableArray array];
  exampleB_addBlockToArray(array);
  void(^block)() = [array objectAtIndex:0];
  block();
}
Example C
void exampleC_addBlockToArray(NSMutableArray *array) {
  [array addObject:^{
    printf("C\n");
  }];
}

void exampleC() {
  NSMutableArray *array = [NSMutableArray array];
  exampleC_addBlockToArray(array);
  void(^block)() = [array objectAtIndex:0];
  block();
}
Example D
typedef void(^dBlock)();

dBlock exampleD_getBlock() {
  char d = 'D';
  return^{
    printf("%c\n", d);
  };
}

void exampleD() {
  exampleD_getBlock()();
}
Example E
typedef void(^eBlock)();

eBlock exampleE_getBlock() {
  char e = 'E';
  void(^block)() = ^{
    printf("%c\n", e);
  };
  return block;
}

void exampleE() {
  eBlock block = exampleE_getBlock();
  block();
}

  答案?不存在的!看在辛苦码这么字的份上,先star一发我就发咯。

参考文献

apple源码地址
参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容