Block是在iOS4引入的新特性,是一种特殊的数据类型,今天我们就从源码层面探索一下Block具体是一种什么类型,并探寻下Block的内存管理方式。
一、Block类型
对于Block是什么类型,其实网上已经给出了答案,那就是Block实例也是一种对象。这个观点是完全正确的,我们可以从以下两个方面进行验证:
1. 源码
目前关于Block的源码是公开的,具体下载位置为地址。
对于Block,本质上是一个结构体,其内容如下:
#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
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
#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
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
从Block_layout
的isa
指针可以得出以上结论。
2. clang转换后的代码
我们可以简单写一个Block:
void(^block1)(void) = ^(void) {
NSLog(@"block1");
};
通过clang -rewrite-objc main.m
得到转换后的代码,其中与该Block有关的内容如下:
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hb_tnc4751s73b8_zwpzmxttpvm0000gn_T_main_f377bb_mi_0);
}
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)};
void(*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
可以看到该Block本身就是一个__main_block_impl_0
的结构体,而该结构体中第一个成员便是struct __block_impl
:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
可以看到,虽然转换出来的代码最终是__block_impl
结构体,但他与源码的结构体内容完全一致,且第一个成员为isa指针,同样可以证明Block变量就是一个对象。
二、Block引用计数
既然Block也是一种对象,那它是否也遵循对象的引用计数方式呢?
我们从runtime的SideTable源码并没有找到任何关于Block引用计数的相关代码,但这并不表示Block和引用计数没有关联,Block与引用计数的关联其实很简单,就是保持在struct Block_layout
结构体本身中:
volatile int32_t flags; // contains ref count
flags
成员的注释中表明了它保持了引用计数的内容,那么他是如何保存的呢?
在struct Block_layout
声明的代码上部就有一些关于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
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
};
结合这些标志,我们可以得出flags
成员的含义:
flags二进制下标 |
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 ... 1 | 0 |
代表含义 |
是否有扩展布局 | 是否有签名 | 返回值是否在栈上 | 是否是全局Block | 是否采用垃圾回收机制 | helper是否有C++代码 | 是否有copy/dispo |