iOS-Block

242 阅读21分钟

身为iOS开发人员熟练使用block是必备技能,也是面试中常考的面试点。对于block来说,它即是对象,也是结构体,还可以是代码块,我们可以方便的将其作为参数函数属性来使用,也正是他的强大,因此被程序员广泛使用,本文就由浅入深的来探探block

Block类型

全局Block

  • 当block在没有捕获外部的局部变量或者只使用了全局变量或静态变量的情况下,block的类型为全局Block
    void (^block)(void) = ^{
    };
    block();
    NSLog(@"%@",block);
    
    打印结果如下:
    <__NSGlobalBlock__: 0x1097a3050>

那么使用静态变量的情况下,如下: image.png

堆Block

  • 当捕获了局部变量并赋值给了强引用的情况下,block的类型为堆Block
    int a = 1;
    void (^block)(void) = ^{
        NSLog(@"%d",a);
    };
    block();
    NSLog(@"%@",block);
    
    打印结果如下:
    <__NSMallocBlock__: 0x600000a66c40>

栈Block

  • 当捕获了局部变量但是赋值给了弱引用,这时的block的类型为栈Block
    int a = 1;
    void (__weak ^block)(void) = ^{
        NSLog(@"%d",a);
    };
    block();
    NSLog(@"%@",block);
     打印结果如下:
    <__NSStackBlock__: 0x7ff7b9ed5f68>

面试题一

看下方代码,打印结果。

        NSObject * objc = [[NSObject alloc]init];
        NSLog(@"-- %ld",CFGetRetainCount((__bridge CFTypeRef)objc));
        
        void(^block1)(void) = ^{
            NSLog(@"-- %ld",CFGetRetainCount((__bridge CFTypeRef)objc));
        };
        block1();
        void(__weak ^block2)(void) = ^{
            NSLog(@"-- %ld",CFGetRetainCount((__bridge CFTypeRef)objc));
        };
        block2();

image.png 分析: 这个其实主要看一点,当block从栈区copy到堆区的时候,捕获变量的引用计数是需要+1的,因此,当对象创建出来,引用计数为1,block1从栈区生成并捕获变量,引用计数又+1,当block1从栈区拷贝到堆区引用计数又+1,然后就是block2在栈上生成并捕获变量导致的+1

面试题二

那么为什么要copy block呢?

  • block在创建的时候,他的内存是分配在栈上的;
  • 栈上的内存由系统来控制,因此创建的对象有可能随时被销毁,销毁后如果再次调用就有可能造成崩溃。
  • 但是在对block进行copy后,block就存放在堆区,他的生命周期就会随着对象的销毁而销毁。
  • 另外,在ARC下,对block使用copystrong其实是一样的,因为blockretain就是用copy实现的,所以ARC下block使用copy和strong都可以。

block底层

那么block是如何copy的呢?要弄懂这个问题,我们需要首先看下block的结构,我们可以通过clang -rewrite-objc命令可以查看下编译后的block代码。

image.png image.png

  • 在这里可以看到block编译成底层后其实是一个__main_block_impl_0的结构体。
  • 并且该结构体中有一个isa,说明block其实也是一个对象
  • 在这里也能发现,在编译期传入的isa的值为_NSConcreteStackBlock的地址,也就是说编译期就已经知道block的类型;
  • 在调用的地方,有传入的捕获的obj对象,因此也说明,捕获变量是在编译期

那么block的底层又是如何copy的呢?我们可以通过汇编来看下调用: image.png image.png 在这里能看到block的底层会调用objc_retainBlock,然后调用_Block_copy函数。 如果我们要看下_Block_copy函数的具体实现就需要看下libclosure源码

Block_layout

在去了解Block_copy函数之前,我们需要看看下Block_layout,也就是对block的封装的结构体:

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    // libffi ->
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
  • isa指针指向Block的类型
  • flags是一个标记位,用于标记Block的类型、状态等(具体类型见下方代码);
  • reserved 保留字段,源码中未看到有使用
  • invoke是Block函数,Block调用实际上就是调用的invoke
  • descriptor是可变属性
// 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_layout结构体就这几个参数,但descriptor是个可变属性,可变在哪里呢?如果我们查看Block_descriptor_1结构体,就会发现其他两个类似的结构体:

#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
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

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

Block_descriptor_2Block_descriptor_3就是有可能要导入的参数,这三个结构体中的内容各不相同

  • Block_descriptor_1 存储block的大小
  • Block_descriptor_2 里面有blockcopy函数dispose函数
  • Block_descriptor_3 里存放了signature签名等信息 如果要看下这三者的联系,可以看下block在copy的时候调用的三个函数:
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
    return aBlock->descriptor;
}

// 取得 block 中的 Block_descriptor_2,它藏在 descriptor 列表中
// 调用者:_Block_call_copy_helper() / _Block_call_dispose_helper
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    // Block_descriptor_2 中存的是 copy/dispose 方法,如果没有指定有 copy / dispose 方法,则返回 NULL
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    // 先取得 Block_descriptor_1 的地址
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    // 偏移 Block_descriptor_1 的大小,就是 Block_descriptor_2 的起始地址
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

// 取得 block 中的 Block_descriptor_3,它藏在 descriptor 列表中
// 调用者:_Block_extended_layout() / _Block_layout() / _Block_signature()
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    // Block_descriptor_3 中存的是 block 的签名,如果没有指定有签名,则直接返回 NULL
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    // 先取得 Block_descriptor_1 的地址
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    // 先偏移 Block_descriptor_1 的大小
    desc += sizeof(struct Block_descriptor_1);
    // 如果还有 Block_descriptor_2,就再偏移 Block_descriptor_2 的大小,得到的就是 Block_descriptor_3 的地址
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}

通过这函数,也可以看到Block_descriptor_2Block_descriptor_3的大体结构:

  • Block_descriptor_1其实就是descriptor,直接返回即可;
  • Block_descriptor_2为偏移Block_descriptor_1大小的地址(Block_descriptor_1Block_descriptor_2的地址紧挨,Block_descriptor_1的尾地址就是Block_descriptor_2的首地址)
  • Block_descriptor_3的地址需要看下有没有Block_descriptor_2,如果没有,只需要偏移Block_descriptor_1的大小即可,如果有,就需要偏移Block_descriptor_1 +Block_descriptor_2的大小。 也就是说着三者在内存上是紧挨着的结构,类似于数组;

我们看下当block在捕获一个常量参数的时候(如下代码),在编译之后生成的cpp文件中,block的结构:

    int a = 10;
    void (^block)(void) = ^ {
        NSLog(@"%d",a);
    };
    block();

编译后:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

这里的__main_block_impl_0为最终编译后的block类型,在加载的时候,又会被强转成Block_layout类型,并且他们的对应关系为:

__main_block_impl_0Block_layout
isa&_NSConcreteStackBlockisa
Flagsflags=0flags
FuncPtrfp闭包地址invoke
Block_sizesizeof(struct __main_block_impl_0)block大小Block_descriptor_1中的size

这也就是存在Block_descriptor_1结构体的情况;

当block在捕获一个对象参数的时候,如下代码:

    NSObject *objc = [NSObject new];
    void (^block)(void) = ^ {
        NSLog(@"%@",objc);
    };
    block();

编译后:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *objc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_objc, int flags=0) : objc(_objc) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = 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->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

通过上下对比是可以看到__main_block_desc_0结构体多了两个参数__main_block_copy_0__main_block_dispose_0,这两个参数最终会在blockcopy的时候调用_Block_object_assign_Block_object_dispose这两个函数,也就是前文说到的copy函数dispose函数,这时对应关系中就变成了:

__main_block_impl_0Block_layout
isa&_NSConcreteStackBlockisa
Flagsflags=0flags
FuncPtrfp闭包地址invoke
Block_sizesizeof(struct __main_block_impl_0)block大小Block_descriptor_1中的size
*copy_Block_object_assigncopy函数
*dispose_Block_object_disposedispose函数

这是存在Block_descriptor_2结构体的情况;至于Block_descriptor_3,也就是方法签名,其是在运行的时候添加到Block_layout中,因此,可以通过打印来看下: image.png

Block_copy

熟悉了Block_layout的结构组成,接下来就看下_Block_copy函数的调用:

// 拷贝 block,
// 如果原来就在堆上,就将引用计数加 1;
// 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1,并且会调用 copy helper 方法(如果存在的话);
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
// 参数 arg 就是 Block_layout 对象,
// 返回值是拷贝后的 block 的地址
// 运行?stack -》malloc
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    // 如果 arg 为 NULL,直接返回 NULL

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    // 强转为 Block_layout 类型
    aBlock = (struct Block_layout *)arg;
    const char *signature = _Block_descriptor_3(aBlock)->signature;
    
    // 如果现在已经在堆上
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        // 就只将引用计数加 1
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        // block 现在在栈上,现在需要将其拷贝到堆上
        // 在堆上重新开辟一块和 aBlock 相同大小的内存
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        // 开辟失败,返回 NULL
        if (!result) return NULL;
        // 将 aBlock 内存上的数据全部复制新开辟的 result 上
        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
        // 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        // 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        // copy 方法中会调用做拷贝成员变量的工作
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        // isa 指向 _NSConcreteMallocBlock
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
  • 这里传入的参数是block,在进入后,就会如前文中所说,把block强转成Block_layout类型;
  • 如果block已经在堆上,就只将引用计数+1
  • 如果block在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
  • 如果block在栈上,现在需要将其拷贝到堆上,这时就会调用_Block_call_copy_helper函数;
// 调用 block 的 copy helper 方法,即 Block_descriptor_2 中的 copy 方法
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    // 取得 block 中的 Block_descriptor_2
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    // 如果没有 Block_descriptor_2,就直接返回
    if (!desc) return;

    // 调用 desc 中的 copy 方法,copy 方法中会调用 _Block_object_assign 函数
    (*desc->copy)(result, aBlock); // do fixup
}

_Block_call_copy_helper函数就会调用前文中说到的_Block_descriptor_2函数来获取_Block_descriptor_2结构体,如果有该结构体,然后调用_Block_object_assign函数来处理捕获的参数(前文中的对照表中*desc->copy其实就是调用_Block_object_assign函数)。

// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
// 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment,
// 参数 destAddr 其实是一个二级指针,指向真正的目标指针
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_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数,
        // 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的
        _Block_retain_object(object);
        // 使 dest 指向的目标指针指向 object
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

       // 使 dest 指向的拷贝到堆上object
        *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 指向的拷贝到堆上的byref
        *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
        *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
        *dest = object;
        break;

      default:
        break;
    }
}

在这里switch case判断的类型有点多,但大体上分为以下几种:

// Runtime support functions used by compiler when generating copy/dispose helpers
// 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.
};

普通对象的copy

普通的对象(不加任何修饰比如__weak, 或者__block)的copy会被Block持有,也就是*desc=object,另外也会去调用_Block_retain_object函数区进行增加引用计数:

      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/
        // 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数,
        // 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的
        _Block_retain_object(object);
        // 使 dest 指向的目标指针指向 object
        *dest = object;

block的copy

如果捕获对象也是个block,那么就直接调用_Block_copy

 case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
       // 使 dest 指向的拷贝到堆上object
      *dest = _Block_copy(object);

__block修饰的对象

__block修饰对象跟普通对象不一样,在block里面它是一个结构体,它的copy实现是在函数_Block_byref_copy中实现的:

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 指向的拷贝到堆上的byref
        *dest = _Block_byref_copy(object);
        break;

接下来就来看下_Block_byref_copy函数:

// 1. 如果 byref 原来在堆上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3,
//    被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable,
//    原来 byref 的 forwarding 也会指向堆上的 byref;
// 2. 如果 byref 已经在堆上,就只增加一个引用计数。
// 参数 dest是一个二级指针,指向了目标指针,最终,目标指针会指向堆上的 byref
static struct Block_byref *_Block_byref_copy(const void *arg) {
    // arg 强转为 Block_byref * 类型
    struct Block_byref *src = (struct Block_byref *)arg;

    // 引用计数等于 0
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        // 为新的 byref 在堆中分配内存
        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
        // 新 byref 的 flags 中标记了它是在堆上,且引用计数为 2。
        // 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack
        // one for caller 很好理解,那 one for stack 是为什么呢?
        // 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了 copy
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        // 堆上 byref 的 forwarding 指向自己
        copy->forwarding = copy; // patch heap copy to point to itself
        // 原来栈上的 byref 的 forwarding 现在也指向堆上的 byref
        src->forwarding = copy;  // patch stack to point to heap copy
        // 拷贝 size
        copy->size = src->size;

        // 如果 src 有 copy/dispose helper
        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
            // 取得 src 和 copy 的 Block_byref_2
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            // copy 的 copy/dispose helper 也与 src 保持一致
            // 因为是函数指针,估计也不是在栈上,所以不用担心被销毁
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            // 如果 src 有扩展布局,也拷贝扩展布局
            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);
                // 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上
                copy3->layout = src3->layout;
            }
            // 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            // 如果 src 没有 copy/dispose helper
            // 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    // src 已经在堆上,就只将引用计数加 1
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

// 引用计数加 1,最多不超过 BLOCK_REFCOUNT_MASK
// volatile的作用是:作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化
static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        // 如果 old_value 在第 1~15 位都已经变为 1 了,即引用计数已经满了,就返回 BLOCK_REFCOUNT_MASK
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        // 比较 where 处的现在的值是否等于 old_value,如果等于,就将新值 oldValue + 2 放入 where
        // 否则继续下一轮循环
        // 这里加 2,是因为 flag 的第 0 位已经被占了,引用计数是第 1~15 位,所以加上 0b10,引用计数只是加 1
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

在具体解释该函数之前,我们要先看一下Block_byref是什么?当我们用__block修饰要捕获的变量时,如下所示:

__block NSObject *objc = [NSObject new];
    void (^block)(void) = ^ {
        NSLog(@"%@",objc);
    };
    block();

其在编译后的代码如下所示:

struct __Block_byref_objc_0 {
  void *__isa;
__Block_byref_objc_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *objc;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_objc_0 *objc; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__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_objc_0 *objc = __cself->objc; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9z_m9cbf92n62b13hr88w4n5ls80000gn_T_main_7419fc_mi_0,(objc->__forwarding->objc));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 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};
    


  __attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = {
        (void*)0,
        (__Block_byref_objc_0 *)&objc,
        33554432,
        sizeof(__Block_byref_objc_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"))};
    
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_objc_0 *)&objc, 570425344));
    
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

可以看到编译后的内容也是非常多,在这里可以捋清一下脉络:

  • 首先被__block修饰的变量被包装成了__Block_byref_objc_0结构体,
  • __forwarding参数的值就是obj对象包装后的地址即__Block_byref_objc_0
  • __Block_byref_id_object_copy__Block_byref_id_object_dispose都是一个函数地址,其最终指向的函数分别为_Block_object_assign_Block_object_dispose
  • 包装block的结构体__main_block_impl_0捕获的参数就是这里的__Block_byref_objc_0结构体,也就是包装后的objc对象。 传入的参数如下:
//这里是__Block_byref_objc_0结构体定义
struct __Block_byref_objc_0 {
  void *__isa;
__Block_byref_objc_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *objc;
};

//这是传入参数 __Block_byref_id_object_copy_131     
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
//这是传入参数 __Block_byref_id_object_dispose_131    
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

//这里是传入参数
 __attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = {
        (void*)0,
        (__Block_byref_objc_0 *)&objc,
        33554432,
        sizeof(__Block_byref_objc_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_byref函数中,该函数所做的事情就比较明朗了

  • 栈上边原本的byref中的forwarding指向自己(编译时通过(__Block_byref_objc_0 *)&objc赋的值)
  • 如果byref原来在栈上,就将其拷贝到堆上,
  • 将堆上byrefforwarding指向自己(也就是copy->forwarding = copy
  • 将栈上byrefforwarding现在也指向堆上的byref(也就是`src->forwarding = copy)
  • 如果byref已经在堆上,就只增加一个引用计数。 其指向关系的修改大致如下图:

image.png

那么为什么这么设计呢?,可以看下如下代码:

    __block FMPerson *objc = [FMPerson new];
    void (^block)(void) = ^ {
        objc.name = @"李四";
        NSLog(@"%@--%@",objc,objc.name);
    };
    objc.name = @"张三";
    NSLog(@"%@--%@",objc,objc.name);
    block();
    打印结果:
     <FMPerson: 0x6000001cc1a0>--张三
     <FMPerson: 0x6000001cc1a0>--李四
  • 在这段代码中,block外边的objc.name=张三的赋值,是对栈区的block赋值,在编译后其真实调用的是包装了捕获对象的byref中的__forwarding所指向的对象obj,如下图所示 image.png
  • 而在block_copy之后,栈区和堆区的__forwarding都指向拷贝到堆区的byref,然后通过objc.__forwarding->objc这种方式来取到所捕获的对象,这样就保证了数据的一致性

Block_copy总结

  • __block修饰的变量在编译过后会变成__Block_byref__XXX类型的结构体,在结构体内部有一个__forwarding的结构体指针,指向结构体本身。
  • block创建的时候是在栈上的,在将栈block拷⻉到堆上的时候,同时也会将block中捕获的对象拷⻉到堆上,然后就会将栈上block__forwarding指针指向堆上block。 这样我们在block内部修改的时候虽然是修改堆上的对象的值,但是因为栈上的对象的__forwarding指针堆的blcok做了关联,因此栈上blcok调用的时候objc.__forwarding就变成了堆blockobjc.__forwarding.objc也就获取到了堆上的捕获的对象。也就达到同步修改的目的。

Block_dispose

看完了block的copy函数,那么dispose函数又是做什么的呢?其实也不难猜到,dispose函数是与copy函数成对存在,也就是用来释放copy的block,当然在编译后的文件中也能看到dispose函数的调用:

image.png 通过源码就能看到_Block_object_dispose函数具体的实现:

// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
// 当 block 和 byref 要 dispose 对象时,它们的 dispose helper 会调用这个函数
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      // 如果是 byref
      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
        // 对 byref 对象做 release 操作
        _Block_byref_release(object);
        break;
      // 如果是 block
      case BLOCK_FIELD_IS_BLOCK:
        // 对 block 做 release 操作
        _Block_release(object);
        break;
      // 如果是对象
      case BLOCK_FIELD_IS_OBJECT:
        // 默认啥也不干,但在 _Block_use_RR() 中可能会被 Objc runtime 或者 CoreFoundation 设置一个 release 函数,里面可能会涉及到 runtime 的引用计数
        _Block_release_object(object);
        break;
      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;
    }
}

普通对象的release

前文中也说到,普通对象的copy是被Block持有,然后调用_Block_retain_object处理引用计数问题,那在释放的时候调用外部的_Block_release_object函数进行引用计数处理就行。 image.png

block的release

如果捕获对象也是个block,就调用_Block_release函数,函数中的逻辑可看注释:

// 对 block 做 release 操作。
// block 在堆上,才需要 release,在全局区和栈区都不需要 release.
// 先将引用计数减 1,如果引用计数减到了 0,就将 block 销毁
void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    // 如果 block == nil
    if (!aBlock) return;
    // 如果 block 在全局区
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    // block 不在堆上
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;

    // 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 block 需要被销毁
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        // 调用 block 的 dispose helper,dispose helper 方法中会做诸如销毁 byref 等操作
        _Block_call_dispose_helper(aBlock);
        // _Block_destructInstance 啥也不干,函数体是空的
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}


__block修饰对象的release

这里就直接调用_Block_byref_release函数:

// 对 byref 对象做 release 操作,
// 堆上的 byref 需要 release,栈上的不需要 release,
// release 就是引用计数减 1,如果引用计数减到了 0,就将 byref 对象销毁
static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    // 取得真正指向的 byref,如果 byref 已经被堆拷贝,则取得是堆上的 byref,否则是栈上的,栈上的不需要 release,也没有引用计数
    byref = byref->forwarding;
    
    // byref 被拷贝到堆上,需要 release
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        // 取得引用计数
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        // 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 byref 需要被销毁
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            // 如果 byref 有 dispose helper,就先调用它的 dispose helper
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                // dispose helper 藏在 Block_byref_2 里
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}

另外要注意的是:__block不可以用于修饰静态变量和全局变量。

循环引用

对于block来说,另一个要注意的店就是循环引用的问题,先看下代码:

self.block = ^{
        self.name = @"fm";
    };

当然这里会产生循环引用,产生循环引用的原因就是在block中使用了self或者成员变量,并最终形成self -> block -> self的引用关系,导致循环引用。

即便把self.name改成_nameblock在捕获变量的时候也会把self对象捕获,最终形成你中有我我中有你的局面。
一般情况下我们可以使用__weak typeof(self) weakSelf = self;也就是弱引用的方式来解决循环引用,但在有GCD的情况下就会带来另外的问题:

-(void)method {
    self.name = @"fm";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.block();
}

在block中有dispatch_after等类似延迟操作时,有可能在延迟的这段时间内,weakSelf.name已经释放,导致此时weakSelf.name的值为null,为了避免类似的问题一般可以通过以下方式来解决该问题:

强弱共舞(weak strong dance)

通过__weak typeof(self) weakSelf = self;__strong typeof(weakSelf) strongSelf = weakSelf;配合使用

-(void)test {
    self.name = @"fm";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%p",&strongSelf);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();
}

临时变量

将产生循环引用的self变成临时变量

-(void)test {
    self.name = @"c++";
    __block FMViewController *vc = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;
        });
    };
    self.block();
}

block参数

self作为参数传递到block

-(void)test {
    self.name = @"fm";
    self.block = ^(FMViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    self.block(self);
}

@weakify、@strongify

如果有使用过RAC的知道,在RAC中一般通过如下方式来解决循环引用

@weakify(self)
    self.block = ^{
        @strongify(self)
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"%@",self);
        });
    };

block外@weakify(self)block内@strongify(self)就解决了循环引用问题,非常方便,那么他又是如何做的呢?
我们可以看下RAC的宏定义weakifystrongify

#define weakify(...)  rac_keywordify  metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

rac_keywordify的宏定义比较简单,如下代码,其实就是autoreleasepool {}的替换

#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif

metamacro_foreach_cxt的参数就比较多了,如果对传入参数做替换后,就如下代码:

#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) 
        metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
        
替换后:
autoreleasepool {}
metamacro_concat(
metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_,  , __weak, self)

metamacro_concat的宏定义如下:

#define metamacro_concat(A, B)   metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B

宏定义中的##为宏连接符,用来把传入的参数连接起来,如metamacro_concat(A, B) 就是把A、B连接起来变成ABmetamacro_argcount宏定义如下

#define metamacro_argcount(...) \
        metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

/**
 * Returns the Nth variadic argument (starting from zero). At least
 * N + 1 variadic arguments must be given. N must be between zero and twenty,
 * inclusive.
 */
#define metamacro_at(N, ...) \
        metamacro_concat(metamacro_at, N)(__VA_ARGS__)

通过注释,可以看出来metamacro_argcount(...)用来替换可变参数(...)的个数;举个例子

  • metamacro_argcount(@"obj") 会被替换成1
  • metamacro_argcount(@"obj",@“obj”) 会被替换成2 所以metamacro_argcount(self) 会被替换成1(只有一个参数) 因此weakify的宏定义就变成了:
autoreleasepool {}
metamacro_foreach_cxt ## 1 (rac_weakify_,  , __weak, self)
也就是:
autoreleasepool {}
metamacro_foreach_cxt1 (rac_weakify_,  , __weak, self)

metamacro_foreach_cxt1的宏定义如下

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

在一一对应替换之后就变成了:

autoreleasepool {}
rac_weakify_(0,__weak,self)

rac_weakify_的宏定义如下

#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

因此@weakify最终就会被替换成下面的样子:

autoreleasepool {}
__weak __typeof__(self) self_weak_ = self

其实也就是__weak typeof(self) weakSelf = self;也可以通过Product > Perform Action > Preprocess看下编译后的结果:

image.png

@strongify也是通过这样的宏定义,变成了: __strong __typeof__(self_weak) self = self_weak,从而解决了循环引用的问题。