iOS 探索 -- Block的探索分析

1. 初识 Block

1. 什么是 Block?

Block 是 C 语言的扩充功能, 可以用一句话来概括就是 带有自动变量 (局部变量) 的匿名函数。它的语法格式如下:

  ^ 返回值类型 参数列表 表达式
  // 举例
  ^ void (int count) {return count + 1;}
复制代码

其中的 返回值类型参数列表(没有参数的时候) 都可以被省略掉, 但是如果省略掉了返回值并且在表达式列有多种返回结果的话必须保证每个 return 的类型保持一致。

2. Block 的分类

在苹果的 libclosure 源码 中可以找到关于 Block 的分类内容, 里面看到一共有 6 种不同的 Block, 但是在我们平时的开发中接触到的只有其中的三种:

 void * _NSConcreteGlobalBlock[32] = { 0 };
 void * _NSConcreteStackBlock[32] = { 0 };
 void * _NSConcreteMallocBlock[32] = { 0 };
 // 下面的三种我们接触不到
 void * _NSConcreteAutoBlock[32] = { 0 };
 void * _NSConcreteFinalizingBlock[32] = { 0 };
 void * _NSConcreteWeakBlockVariable[32] = { 0 };
复制代码

image-20220613225721660

那么这三种 Block 都有什么特点呢 ?

首先他们各自是存储在不同的区域里面的:

种类 存储的区域
_NSConcreteGlobalBlock 全局区的已初始化数据区 (.data 区)
_NSConcreteStackBlock 栈区
_NSConcreteMallocBlock 堆区

然后就是可以根据当前工程的环境 (MRC 或者 ARC) 以及是否有访问外部的变量来判断 Block 的类型:

  1. 没有访问外部变量

Block 为 _NSConcreteGlobalBlock

  1. 在全局变量区域定义的 Block

Block 为 _NSConcrateGlobalBlock

  1. 访问了外部变量

MRC 环境下: 默认是 _NSConcreteStackBlock, 但是为了安全起见一般需要使用 copy 方法将其进行拷贝到堆上。

ARC 环境下: 访问了外部变量的话就是 _NSConcreteMallocBlock, 因为系统自动帮我们把 Block 拷贝到了堆区。

2. Block 探索

上面介绍了 Block 的定义和分类相关的内容, 接下来主要通过 clang 将其进行转化来研究一下 Block 的本质和一些其他的实现原理:

1. Block 的本质

1. 没有变量捕获

 int main(){
     void(^block)(void) = ^{
         printf("123456");
     };
     block();
     return 0;
 }
复制代码

使用 clang 将上面的代码重新编译成 cpp 文件得到下面的代码:

 //
 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;
   }
 };
 // __block_impl
 struct __block_impl {
   void *isa;
   int Flags;
   int Reserved;
   void *FuncPtr;
 };
 //
 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     printf("123456");
 }
 //
 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)};
 // main 函数的部分
 int main(){
     void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
     block->FuncPtr(block);
     return 0;
 }
复制代码
  1. 发现 Block 在经过编译后变成了一个 __main_block_imp 的结构体, 结构体包含两个属性 implDesc

  2. impl (也就是__block_impl) 的结构体声明里发现了他的相关属性:

    • isa : 在构造函数里将 Block 的类型赋值给了 isa , 先不说别的, 当看到 isa 的时候就应该想到他在 iOS 中意味着什么, Block 就是 OC 对象。
    • Flags : 里面记录了 Block 的一写信息, 这个后面会介绍
    • Reserved : 应该是后面为了扩充的保留
    • FuncPtr : 指向 Block 方法块的指针
  3. main 函数的部分发现, block 通过构造函数把编译生成的 __main_block_fun_0 (也就是包含打印的方法块) 赋值给了 FuncPtr , 在最后通过 block->FuncPtr(block) 调用

2. 含有变量捕获

含有变量捕获的情况根据变量可以分成三种: 局部变量、静态变量、全局变量 (可以试着想一下他们的存储区域)

类型 是否被捕获 访问变量方式
局部变量 值传递
静态局部变量 指针传递
全局变量 直接访问
1. 局部变量
 int main(int argc, const char * argv[]) {
     int a = 10;
     void(^block)(void) = ^{
         NSLog(@"a = %d",a);
     };
     a = 25;
     block();
     return 0;
 }
 // 结果
     a = 10
复制代码

使用 clang 进行重新编译:

 // __main_block_impl
 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_func
 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   int a = __cself->a; // bound by copy
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_yk_c29rfw5n47719qtm1m2rm97h0000gq_T_main_48753d_mi_0,a);
 }
 // main
 int main(int argc, const char * argv[]) {
     int a = 10;
     void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
     a = 25;
     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
     return 0;
 }
复制代码
  1. 在 Block 的结构体中出现了一个新的属性 a
  2. main 方法里创建 Block 的时候, 是直接将 局部变量a 的值传递了进去, 然后赋值给了Block 结构体中的 a 属性
  3. 在调用方法的里面 int a = __cself->a; 这里的 self 是谁, 在 main 方法里是这样调用的 block->FunPtr(block); , 所以这里的 self 是指的 Block 本身。在方法调用时, 打印的其实是 Block 创建时值拷贝到自身的 a, 所以外部的局部变量的改变并不会影响到 Block 内部。 同样的, Block 的内部如果修改 a 的值也无法改变外部的 (Block 内部修改的话可以通过一个参数把 a 传进去, 这里就不贴代码了) 。因为他们俩其实是两个不同的变量。
2. 局部静态变量
 //
 int main(int argc, const char * argv[]) {
     int a = 10;
     static int b = 20;
     void(^block)(void) = ^{
         NSLog(@"a = %d\n b = %d",a,b);
     };
     a = 25;
     b = 40;
     block();
     return 0;
 }
 // 结果
    a = 10
    b = 40
复制代码

发现静态变量 b 的值的改变竟然在 Block 里也改变了, 这里又发生了什么, 通过 clang 来看一下:

 // __main_block_impl
 struct __main_block_impl_0 {
   struct __block_impl impl;
   struct __main_block_desc_0* Desc;
   int a;
   int *b;
   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
   }
 };
 // __main_block_func
 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   int a = __cself->a; // bound by copy
   int *b = __cself->b; // bound by copy
         NSLog((NSString *)&__NSConstantStringImpl__var_folders_yk_c29rfw5n47719qtm1m2rm97h0000gq_T_main_169e03_mi_0,a,(*b));
  }
 // main
 int main(int argc, const char * argv[]) {
     int a = 10;
     static int b = 20;
     void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));
     a = 25;
     b = 40;
     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
     return 0;
 }
复制代码

这里只需要贴这两块的代码就可以看出来区别了, 导致结果不同的原因在于:

  1. 如果是静态变量的话, Block 中出现的属性是 *b 一个指针变量
  2. 并且在创建 Block 的时候是把静态变量 b 的地址传递过去了, 经过赋值后 Block 只是复制了静态变量 b 的地址, 自己内部的指针 *b 与外部的静态变量 b 是指向的同一块内存空间
  3. 方法调用的时候传递的参数也是 *b , 访问的是同一块内存
3. 全局变量
// 
int c = 1;
static int d = 30;

int main(int argc, const char * argv[]) {
    int a = 10;
    static int b = 20;
    void(^block)(void) = ^{
        NSLog(@"a = %d\n b = %d\nc = %d\nd = %d",a,b,c,d);
    };
    a = 25;
    b = 40;
    c = 50;
    d = 60;
    block();
    return 0;
}
// 结果
	a = 10
    b = 40
    c = 50
    d = 60
复制代码

打印结果, 无论是全局变量 c 还是全局静态变量 d 被改变后都会影响到 Block 内部的打印结果。

// __main_block_impl
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *b;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
复制代码

经过 clang 转化后发现, Block 没有去捕获全局变量, 他是直接进行全局变量访问的, 没有做任何事情。

2. __Block

1. __Block 做了什么

上面介绍了 Block 的变量捕获相关, 但是我们平时使用中可以通过一个 __Block 的关键字标记来达到可以在 Block 内部去改变原本无法直接访问的变量的目的, 这个关键字标记的情况到底发生了什么。

int main(int argc, const char * argv[]) {
    __block int a = 10;
    void(^block)(void) = ^{
        a++;
        NSLog(@"a = %d",a);
    };
    block();
    return 0;
}
// 结果
	a = 11;
复制代码

编译后:

// __main_block_impl
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// __Block_byref_a 
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
复制代码

首先可以看到 __main_block_impl 结构体内部多了一个 __Block_byref_a 的结构体指针, 不再像是之前那样 (int a)。__Block_byref_a 结构体内有一个变量 a, 还有 __isa__forwarding 等一些值。//

// main 函数
int main(int argc, const char * argv[]) {
     __Block_byref_a_0 a = {
         (void*)0,
         (__Block_byref_a_0 *)&a,
         0,
         sizeof(__Block_byref_a_0),
         10};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
// __main_block_func
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a)++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yk_c29rfw5n47719qtm1m2rm97h0000gq_T_main_67c9d5_mi_0,(a->__forwarding->a));
    }
复制代码

main 函数中:

  1. 首先是创建了一个 __Block_byref_a 的结构体对象, 在对象里把 __forwarding 指针变量指向了自己, 然后给自己内部的 a 变量赋值 10, 最后把 结构体 a 赋值给了 Block
  2. 在调用方法的内部, 先是从 Block 中取出结构体 a , 然后通过 a->__forwarding->a++ 的方式修改了 a 变量的值

所以通过 __block 标记后创建的变量不再是一个简简单单的变量了, 而是一个 __Block_byref_a 结构的对象, 对象里面包含有一个变量 a

2. __Block 的 copy

除了上面的不同代码外, 还发现了一些其他的代码, 下面来看一下:

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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};
复制代码

前面的介绍知道 Block 在使用外部变量后会被从 栈空间 拷贝到 堆空间 (ARC 下), 这里的 copy 方法是用来干什么的呢 ? 下面通过源码来看看:

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_retain_object(object);
        *dest = object;
        break;

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

        *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 = _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;
        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;
        break;

      default:
        break;
    }
}
复制代码

从编译过的代码可以知道, 传进来的值是 8, 所以应该来到 *dest = _Block_byref_copy(object); , 从注释里可以得知是 将栈空间上的 __block 容器 拷贝到堆空间上

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

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        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
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        
        // 问题 - __block 修饰变量 block具有修改能力
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        
        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
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            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);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            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) {
        latching_incr_int(&src->forwarding->flags);
    }
    return src->forwarding;
}
复制代码

大致流程如下:

  1. 创建了一个 src 来保存要拷贝的 Block_byref对象
  2. 根据 flags 标记判断是否已经进行过拷贝, 如果已经拷贝过了就调整引用计数 (flags 存储的信息会在下面列出来)
  3. 如果没有拷贝过, 就创建一个新的 Block_byref 对象 copy 然后进行拷贝, 拷贝包括一些 赋值操作 和 一些状态的改变等等, 但是有一个关键的地方在 4 进行说明
  4. copy->forwarding = copy;src->forwarding = copy; , 从前面的介绍我们已经知道, 没有经过拷贝的时候 Block_byrefforwarding 是指向自己的, 并且在访问变量值的时候也是通过 forwarding 来进行访问。在这里拷贝完后, 旧的对象 srcforwarding 指向了新的对象, 也就是说, src 通过 forwarding 访问的变量 和 copy 通过 forwarding 访问的变量都是经过拷贝后的堆区的变量了。

image-20220614164525747

flags 存储的各种信息:

// 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
    // 是否有 C++ 代码
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    // 是否有垃圾回收
    BLOCK_IS_GC =             (1 << 27), // runtime
    // 是否是全局 block
    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
};
复制代码

3. __Block 的 release

前面看了 copy 的操作, 在来看看 release 做了什么:

//
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
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
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _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;
    }
}
//
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->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}
复制代码

release 的话没有什么好说的了, 要提一点的是这一行 byref = byref->forwarding; // 取消对转发指针的引用,因为编译器不再这样做了

3. Block 的 copy 分析

前面研究了 Block 对 __block修饰的变量的 copy 操作, Block 在各个区之间的操作是怎么实现的呢, 来通过源码了解一下:

// 源码中 Block.h 的相关方法声明
// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not use these variables yourself.
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteStackBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// _Block_copy
// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        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
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
复制代码

步骤:

  1. 根据 flags 标志位判断引用计数相关内容

  2. 如果是全局 Block 的话, 直接返回

  3. 如果是栈 Block, 进行 copy 到堆的操作

    • 通过原 Block 中保存的 size 给新的 Block 申请开辟空间
    • 通过 memmove 将数据从 栈区 Block 拷贝到 堆区的 Block
    • 修改 invokeflags 中的相关记录数据
    • 给堆区 Block 的 isa 赋值 _NSConcreteMallocBlock

3. Block 的循环引用问题

搞完了 Block 的相关分析, 最后再来看看 Block 使用中的问题 - 循环引用。

// typedef void(^MyBlock)(void);
// @property (nonatomic, copy) MyBlock block;
// @property (nonatomic, copy) NSString *name;
- (void)test {
    self.name = @"C";
    self.block = ^{
        NSLog(@"%@", self.name);
    };
    self.block();
}
//
- (void)dealloc{
    NSLog(@"dealloc 来了");
}
复制代码

上面的例子系统会有一个警告:

Capturing 'self' strongly in this block is likely to lead to a retain cycle

struct __ViewController__test_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__test_block_desc_0* Desc;
  ViewController *self;
  __ViewController__test_block_impl_0(void *fp, struct __ViewController__test_block_desc_0 *desc, ViewController *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
复制代码

self -> block -> self

然后程序虽然可以跑起来, 但是会当前的控制器无法被释放, 原因在于: self 持有当前的 block , block 又会在内部持有 self 的指针, 会形成一个环导致了他们两个都无法被释放。那么怎么解决 Block 造成的循环引用呢:

1. 使用 __weak 解决循环引用

- (void)test {
    self.name = @"C";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%@", weakSelf.name);
    };
    self.block();
}
复制代码

weakSelf->self->block->weakSelf

如上图, 给 Block 持有一个弱引用的 weakSelf , 这样就可以打破强持有关系了, 但是这样会存在另外一个问题:

- (void)test {
    self.name = @"C";
    __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();
}
// 结果
	dealloc 来了
    (null)
复制代码

block 块里面加了一个延时操作, 然后就造成了 weakSelf 已经被释放了, 但是 block 才执行到打印方法, 导致结果出现了 (null) 。解决办法:

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

strongSelf->weakSelf->self->block->strongSelf

为了避免这种状况出现, 需要在 Block 的内部再做一次强持有, 保证 self 释放的时候 block 已经执行完毕了。strongSelf 为局部变量, 他的作用域在 Block 内, 这样就保证了 Block 块的正常执行, 同时有防止了循环引用问题。

2. 中介者模式

- (void)test {
    self.name = @"C";
    __block ViewController *vc = self;
    self.block = ^{
        NSLog(@"%@", vc.name);
        vc = nil;
    };
    self.block();
}
复制代码

中介者模式是通过使用 __block 来创建一个中介者对象, 但是必须保证 vc = nil; 的执行, 不然的话还是会造成循环引用问题。

3. 参数传递模式

// block 有一个 ViewController 类型的参数
- (void)test {
    self.name = @"C";
    self.block = ^(ViewController *vc){
        NSLog(@"%@", vc.name);
    };
    self.block(self);
}
复制代码

self 通过参数的形式传递进去, 也可以避免循环引用问题。

最后

关于 Block 的分析就到底结束了, 对于Block 的话平时使用的还是很频繁的, 但是对于他的实现方面确实没有好好的总结过。写的可能不是很好, 假如有哪里不对的地方还请指正。

猜你喜欢

转载自juejin.im/post/7109284650530390029