iOS - Block类型简介

1,381 阅读3分钟

概述

代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,Block是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,特殊地,Block还可以保存一段代码,在需要的时候调用,目前Block已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调

block 会在编译过程中,会被当做结构体进行处理。 其结构Block-ABI-Apple大概是这样的:

struct Block_literal_1 {
   void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
   int flags;
   int reserved;
   void (*invoke)(void *, ...);
   struct Block_descriptor_1 {
   unsigned long int reserved;         // NULL
       unsigned long int size;         // sizeof(struct Block_literal_1)
       // optional helper functions
       void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
       void (*dispose_helper)(void *src);             // IFF (1<<25)
       // required ABI.2010.3.16
       const char *signature;                         // IFF (1<<30)
   } *descriptor;
   // imported variables
};

isa 指针会指向 block 所属的类型,用于帮助运行时系统进行处理。 Block 常见的类型有三种,分别是_NSConcreteStackBlock _NSConcreteMallocBlock _NSConcreteGlobalBlock 。 另外还包括只在GC环境下使用的 _NSConcreteFinalizingBlock _NSConcreteAutoBlock _NSConcreteWeakBlockVariable

下面摘自 libclosure-65 – Block_private.h-213

// the raw data space for runtime classes for blocks
// class+meta used for stack, malloc, and collectable based blocks
BLOCK_EXPORT void * _NSConcreteMallocBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// declared in Block.h
// BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
// BLOCK_EXPORT void * _NSConcreteStackBlock[32];

Block的几种类型

  1. _NSConcreteGlobalBlock & _NSConcreteStackBlock
  2. _NSConcreteMallocBlock
  3. _NSConcreteFinalizingBlock & _NSConcreteAutoBlock
  4. _NSConcreteWeakBlockVariable

_NSConcreteGlobalBlock & _NSConcreteStackBlock

_NSConcreteGlobalBlock& _NSConcreteStackBlock是 block 初始化时设置的类型。 在以下情况中,block 会初始化为_NSConcreteGlobalBlock:

  1. 未捕获外部变量。 在 static void computeBlockInfo(CodeGenModule &CGM, CodeGenFunction *CGF,CGBlockInfo &info) 函数内的 334行 至 339行,通过判断 block(以及嵌套的block) 是否捕捉了本地存储(原文为:local storage),未捕获时,block 会初始化为_NSConcreteGlobalBlock
  if (!block->hasCaptures()) {
    info.StructureType =
      llvm::StructType::get(CGM.getLLVMContext(), elementTypes, true);
    info.CanBeGlobal = true;
    return;
  }
  1. 当需要布局(layout)的变量的数量为0时。 在static void computeBlockInfo(CodeGenModule &CGM, CodeGenFunction *CGF,CGBlockInfo &info)函数内,通过计算 block 的布局(layout)。当需要布局的变量为0时,block 会初始化为_NSConcreteGlobalBlock

统计需要布局(layout)的变量:

  • this (为了访问 c++ 的成员变量和函数,需要 this 指针)
  • 依次按下列规则处理捕获的变量:
    • 不需要计算布局的变量:
      • 生命周期为静态的变量(被 const static 修饰的变量,不被函数包含的静态常量,c++中生命周期为静态的变量)
      • 函数参数
    • 需要计算布局的变量:被 __block 修饰的变量,以上未提到的类型(比如block) //Tips:当需要布局(layout)的变量的统计完毕后,会按照以下顺序进行一次稳定排序。
  • __strong 修饰的变量
  • ByRef 类型
  • __weak 修饰的变量
  • 其它类型

_NSConcreteMallocBlock

在非垃圾收集环境下,当_NSConcreteStackBlock类型的block 被真正复制时,在_Block_copy_internal 方法内部,会转换为 _NSConcreteMallocBlock libclosure-65/runtime.c

// Its a stack block.  Make a copy.
if (!isGC) {
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return NULL;
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // reset refcount
    result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
    result->isa = _NSConcreteMallocBlock;
    _Block_call_copy_helper(result, aBlock);
    return result;
}

_NSConcreteFinalizingBlock & _NSConcreteAutoBlock

在垃圾收集环境下,当 block 被复制时,如果block 有 ctors & dtors 时,则会转换为 _NSConcreteFinalizingBlock类型,反之,则会转换为_NSConcreteAutoBlock 类型

if (hasCTOR) {
    result->isa = _NSConcreteFinalizingBlock;
}
else {
    result->isa = _NSConcreteAutoBlock;
}

_NSConcreteWeakBlockVariable

GC环境下,当对象被__weak __block修饰,且从栈复制到堆时,block 会被标记为 _NSConcreteWeakBlockVariable  类型。

bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
// if its weak ask for an object (only matters under GC)
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy;  // patch stack to point to heap copy
copy->size = src->size;
if (isWeak) {
  copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning
}