iOS底层面试参考点

FBRetainCycleDetector中获取block强引用

2019-09-29  本文已影响0人  初心丶可曾记

FBRetainCycleDetector中获取block强引用的对象实现方式

在我的上一篇文章中介绍了如何获取block捕获的对象,思路是通过解析block内部的layout签名串。最近看FBRetainCycleDetector源码时发现它用了一种十分巧妙的方式获取,第一见到时我也被这种新奇的方式惊艳到,下面就开始正文看下它是如何做的。

获取block强引用的相关代码在FBBlockStrongLayout.m中的static NSIndexSet *_GetBlockStrongLayout(void *block)函数中,如下所示:

static NSIndexSet *_GetBlockStrongLayout(void *block) {
  struct BlockLiteral *blockLiteral = block;

  /**
   BLOCK_HAS_CTOR - Block has a C++ constructor/destructor, which gives us a good chance it retains
   objects that are not pointer aligned, so omit them.

   !BLOCK_HAS_COPY_DISPOSE - Block doesn't have a dispose function, so it does not retain objects and
   we are not able to blackbox it.
   */
  if ((blockLiteral->flags & BLOCK_HAS_CTOR)
      || !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
    return nil;
  }

  void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper;
  const size_t ptrSize = sizeof(void *);

  // 计算一个block的size能够存放多少指针大小的数据,向上取整
  const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;

  // 通过数组构造一个虚拟block
  void *obj[elements];
  //保存detector对象
  void *detectors[elements];

  for (size_t i = 0; i < elements; ++i) {
    FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
    obj[i] = detectors[i] = detector;
  }

  @autoreleasepool {
  //调用block的析构函数,这个obj就是手动构造的虚拟block
    dispose_helper(obj);
  }

  // Run through the release detectors and add each one that got released to the object's
  // strong ivar layout.
  NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];

  for (size_t i = 0; i < elements; ++i) {
    FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
    if (detector.isStrong) {
      [layout addIndex:i];
    }

    // Destroy detectors
    [detector trueRelease];
  }

  return layout;
}

首先是将block强转成struct BlockLiteral类型的block底层结构,然后通过BLOCK_HAS_COPY_DISPOSE这个标识位查看block是否有析构函数,如果没有则代表block没有捕获对象直接返回nil。如果有析构函数则获取到block中的析构函数指针,这个析构函数的作用主要就是对block中捕获的强引用对象调用release方法。然后后面获取到block的size,并计算这个size能存放多少个指针大小的数据,64位系统下也即是size/8向上取整的结果。后面就到了整个实现最巧妙的地方了,首先是创建了两个数组,obj和detectors,这两个数组的大小和block的大小是一致的,然后用FBBlockStrongRelationDetector对象填充这两个数组,然后调用block的析构函数dispose_helper(obj),可以看到这里是将obj数组传进去了,所以这里obj就是一个手动构造的虚拟block,我们先来假设有这这样一段代码

    NSObject *obj_strong1 = [NSObject new];
    NSObject *obj_strong2 = [NSObject new];
    int aVal = 10;
    int bVal = 20;
    
    void (^aBlock)(void) = ^{
        [obj_strong1 description];
        [obj_strong2 description];
        int c = aVal + bVal;
    };

可以看出aBlock是捕获了两个强引用对象和两个int类型变量。那么通过数组构造的那个虚拟block如下所示

图1

我说过dispose_helper方法是对block捕获的对象调用release方法,所以上图中红框圈出的部分会调用release,也即是[detctor release];detctor是FBBlockStrongRelationDetector类型,我们来看看这个类的实现。

图2

注意到它重写了release方法,并且在release调用的时候将_strong设置为YES,也就是说dispose_helper(obj)调用的时候图1中index为4,5的detector对象会进入到release方法并标记为strong。

如此就找到了强引用对象,后面就是遍历detectors数组,然后将strong == YES的对象筛选出来并保存。最后因为重写了release方法,对象并没有真正释放,还需要调用trueRelease进行收尾工作。

不得不说这是一个非常巧妙的方法,不仅需要对block的底层结构非常了解,还需要对内存分布有着充分了解。但是这种方法仍然有着局限性,例如 __block id obj = [NSObject new];这种通过__block修饰的对象并不能获取,因为__block修饰的变量底层转成了byref的结构体变量,虽然其也有isa指针,但是实际它的isa是设置为0,它并不是一个NSObject类型的对象,也就不会调用到release方法。但在实际开发中这种__block id obj形式非常少见,所以也无伤大雅。

上一篇 下一篇

猜你喜欢

热点阅读