21·iOS 面试题·__block 在 ARC 和 MRC 下含义一样吗?

前言

对于 Block 以及 __block 相关的知识点,之前的面试题已经涉及过:04·iOS 面试题·Block 的原理,Block 的属性修饰词为什么用 copy,使用 Block 时有哪些要注意的?,20·iOS 面试题·请解释以下 keywords 的区别: assign vs weak, __block vs __weak。

但是为了保持 MrPeak-如何面试 iOS 工程师? 中面试题的完整性,我们这里会按照顺序来学习这 20 道面试题。由于之前已经聊过 Block 了,所以我们这篇只会粗略的说明 __block 在 ARC 和 MRC 环境下的区别,对于 Block 和 __block 更详细的的知识点,可以看看之前两篇相关的面试题。

__Block 的作用

我们知道 Block 是匿名函数指针,可以截获外部变量。Block 截获外部变量是将外部变量作为成员变量追加到 Block 结构体中,但是匿名函数存在作用域的问题,这个就是为什么我们不能在 Block 内部去修改普通外部变量的原因。所有就出现了 __block 修饰符,以便解决作用域这个问题。

我们先看下 __block 修饰的变量,转换成 C++ 代码是什么样子的:

//Objective-C 代码
 - (void)blockDataBlockFunction {
 __block int a = 100;  ///在栈区
 void (^blockDataBlock)(void) = ^{
 a = 1000;
 NSLog(@"%d", a);
 };  ///在堆区
 blockDataBlock();
 }

//C++ 代码
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __BlockStructureViewController__blockDataBlockFunction_block_impl_0 {
  struct __block_impl impl;
  struct __BlockStructureViewController__blockDataBlockFunction_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
};

用 __block 修饰的变量,会生成一个 Block_byref_a_0 结构体来表示外部变量,然后再追加到 Block 结构体中。这里生成 Block_byref_a_0 这个结构体大概有两个原因:

  1. 抽象出一个结构体,可以让多个 Block 同时引用这个外部变量;
  2. 好管理,因为 Block_byref_a_0 中有个非常重要的成员变量 forwarding 指针,这个指针非常重要(这个指针指向 Block_byref_a_0 结构体),这里可以保证当我们将 Block 从栈拷贝到堆中,之后修改的变量都是同一份。

__Block 在 ARC 和 MRC 环境的区别

结论先行:

  • MRC 环境下,block 截获外部用 __block 修饰的变量,不会增加对象的引用计数
  • ARC 环境下,block 截获外部用 __block 修饰的变量,会增加对象的引用计数

所以,在 MRC 环境下,可以通过 __block 来打破循环引用,在 ARC 环境下,则需要用 __weak 来打破循环引用。

将 block 从栈区拷贝到堆区,需要调用 block 的 copy 方法,copy 方法实际上调用的是 Block_object_assign 方法,我们这里看一下底层实现:(这里主要是通过 block 的 flag 来确定是否增加对象的引用计数。)

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    
    const void **dest = (const void **)destArg;
    switch ((flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);  //object 引用计数+1
        *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];
         ********/

            /// 在mrc的情况下,你对对象添加__block, block是不会对这个对象引用计数+1
        *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;
    }
}

在 _Block_object_assign 中,是通过 flags 来对 object 对象来执行不同的操作,在 MRC 环境下,会走 case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK: 这个 case,只会引用 object 这个对象,并不会对 object 进行 _Block_retain_object 来增加引用计数。

总结

通过上面两点,我们可以知道 __block 在 ARC 和 MRC 环境下是有区别的:在 MRC 环境下,可以通过 __block 来打破循环引用,在 ARC 环境下,则需要用 __weak 来打破循环引用。

参考文献

04·iOS 面试题·Block 的原理,Block 的属性修饰词为什么用 copy,使用 Block 时有哪些要注意的?

20·iOS 面试题·请解释以下 keywords 的区别: assign vs weak, __block vs __weak。

你可能感兴趣的:(21·iOS 面试题·__block 在 ARC 和 MRC 下含义一样吗?)