Block的本质
Block 对象是c语言的语法和运行时结构。很像c函数,但是执行代码的时会使用栈区和堆区的变量。因此,一个block执行的时候,它所维持的这些状态和数据会影响它的行为。虽然block是C语言语法,并且能在c和C++中使用,但是他一个OC对象:
它的底层结构是一个结构体,结构体中的函数指针指向block的函数实现,block()调用的就是这个函数。
Block的类型
跟普通对象一样,Block也有isa指针指向相应的类型。Block有三个类型(NSGlobalBlock,NSMallocBlock,NSStackBlock)。
全局Block(NSGlobalBlock)
Block在不引用外部变量或只引用全局变量或者静态变量就是全局Block。代码示例:
//不引用任何变量
void (^block1)(void) = ^{
};
NSLog(@"block1:%@",block1);
//引用静态变量
static int c = 0;
void (^block2)(void) = ^{
c = 1;
};
NSLog(@"block2:%@",block2);
打印结果:
2021-07-21 17:23:53.944309+0800 001---BlockDemo[32880:11497834] block1:<NSGlobalBlock: 0x10c92a0a0>
2021-07-21 17:23:53.944616+0800 001---BlockDemo[32880:11497834] block2:<NSGlobalBlock: 0x10c92a0c0>
堆Block(NSMallocBlock)
引用了局部变量,发生copy操作之后就变成堆block。代码示例:
//引用局部变量a
int a = 0;
//这里默认是强引用,发生了copy操作
void (^block3)(void) = ^{
NSLog(@"a:%@",@(a));
};
NSLog(@"block3:%@",block3);
打印结果:
2021-07-21 17:32:21.386941+0800 001---BlockDemo[32934:11503105] block3:<NSMallocBlock: 0x600002227cf0>
栈Block(NSStackBlock)
引用了局部变量,未发生copy操作。代码示例:
//引用局部变量a
int a = 0;
//这里__weak是弱引用,未发生了copy操作
void (^__weak block4)(void) = ^{
NSLog(@"a:%@",@(a));
};
NSLog(@"block4:%@",block4);
打印结果:
2021-07-21 17:35:39.843363+0800 001---BlockDemo[32992:11509042] block4:<NSStackBlock: 0x7ffee2080488>
Block的底层结构-Block layout
Block_layout是block结构体的底层结构,其源码如下:
struct Block_layout {
void *isa; //
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;// block()通过它调用函数
struct Block_descriptor_1 *descriptor; //
// imported variables
};
- isa 指针指向Block的类型;
- invoke 是Block函数,Block调用实际上就是调用的invoke
- flags 是个标记位,用于标记Block的类型、状态等
//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
//};
- descriptor是可变属性,而且是递增的。默认的是:
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
当block引用对象时,就会新增如下结构:
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
BlockCopyFunction copy用于保存对象的copy函数,因为如果block引用对象,当Block发生copy时,引用的对象也要copy(Block copy时有解析)。此时的descriptor相当于:
struct Block_descriptor {
uintptr_t reserved;
uintptr_t size;
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
//依据是编译成C++代码时增加了:
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有签名时,增加Block_descriptor_3:
#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
};
Block捕获外部变量
普通变量的捕获
Block捕获外部普通变量(不是__block等修饰的变量)会自动生成一个属性来保存。接下来我们通过将main.c文件编译成main.cpp文件查看底层c++代码进行验证。首先在main函数实现如下block代码:
#import <UIKit/UIKit.h>
int main(int argc, char * argv[]) {
int a = 11;
void (^block)(void) = ^(void){
NSLog(@"%@",@(a));
};
block();
}
通过命令xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m 编译成main.cpp文件,打开,找到main函数对应的c++代码,如下:
int main(int argc, char * argv[]) {
int a = 11;
//这一行是block的创建
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
//这一行是block调用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
//为了便于分析,把类型转换符号去掉,可以简化成:
void (*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
//这一行是block调用,可以看到block调用的时候他会把自身作为参数再次传到函数里面,这一步的用处在后面的__main_block_func_0会讲解
block->FuncPtr(block);
}
-
__main_block_impl_0结构体分析
__main_block_impl_0
是Block编译时自动生成的结构体,Block的创建实际上就是这个结构调用了构造函数创建的,其结构体代码如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; //自动生成a属性来保存捕获的变量a
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {// 构造函数
impl.isa = &_NSConcreteStackBlock;//isa指针
impl.Flags = flags;//
impl.FuncPtr = fp;//fp函数作为参数传入,保存在FuncPtr指针,这样block()才能调用函数
Desc = desc;
}
};
在这里可以看到__block_impl
的构造函数跟我们前面的Block_layout
结构体是对应关系:
isa 对应 isa
Flags 对应falgs
FuncPtr 对应 invoke
Desc 对应 descriptor
-
为捕获的变量自动生成属性以及descriptor的确定
__main_block_impl_0
在__block_impl
的基础上增加了属性int a用于捕获的外部变量a;同时还增加可变属性__main_block_desc_0* Desc
,这个Desc的可变怎么体现呢?比如上面的demo把a是一个值类型(int),它对应的__main_block_desc_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)};
但是如果把a换成对象类型,比如:
NSObject *obj = [[NSObject alloc] init];
void (^block1)(void) = ^{
NSLog(@"%@",obj);
};
block1();
这时候编译的到的_main_block_desc_0
变成:
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_layout
的分析是一致的,它增加了copy
和dispose
方法。因为对象类型需要这两个方法而值类型不需要。
-
被捕获的外部变量的访问
__main_block_func_0
就是block()调用的函数的实现。我们创建Block的时候并没有传入函数名称,但是Block却自动生成一个函数实现,这也是block被称为匿名函数的由来。__main_block_func_0
它的实现代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//函数内又会自定义一个临时变量copy了block结构体中cself->a的值
int a = __cself->a; // // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_91163dcd57j_zw_xyry904bc0000gn_T_main_c2c542_mi_0,((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), (int)(a)));
}
通过源码我们发现__main_block_func_0
的参数实际上是Block结构体本身,这样也就解释了为什么我们能在Block代码块里面访问其结构体__main_block_impl_0
中的属性了,比如这里的int a属性,这里面的a实际上已经不是block外面的变量a,而是__main_block_func_0
自定义的临时变量用于接收Block结构体属性a。这同时也解释了为什么在Block里面无法对a进行修改。
那为什么__block修饰的变量可以被修改呢?它是如何被捕获的?加下来我们看看__block修饰的变量的捕获。
__block变量的捕获
用__block对int a进行修饰,同时在block内对a++操作,研究一下__block的实现原理及a为何能被修改。代码如下:
__block int a = 11;
void (^block)(void) = ^(void){
a++;
NSLog(@"%@",@(a));
};
block();
重复上面的编译步骤,得到编译后main函数的c++代码如下:
int main(int argc, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 11};
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);
}
为方便阅读,简化成如下代码:
int main(int argc, char * argv[]) {
__Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
11
};
void (*block)(void) = __main_block_impl_0(
_main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_a_0 *)&a,
570425344
);
block->FuncPtr(block);
}
-
__main_block_impl_0结构体分析
此时的Block的结构体__main_block_impl_0 代码如下:
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;
}
};
此时的__main_block_impl_0
和捕获普通对象时的结构基本是一样的,唯一不同在于对外部变量的捕获。
-
自动生成一个结构体属性用于接收外部__block变量
通过上面的代码可以看出这段代码比前面的多了一个结构体__Block_byref_a_0,这个结构体实际上Block用来捕获__block int a 变量的,其结构体源码如下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;// 保存变量a的指针
int __flags;
int __size;
int a;// 保存变量a的值
};
-
为什么__block 变量可以在Block里面修改
由代码可以看出Block不仅捕获__block int a 变量的值,还捕获了a的指针,通过__Block_byref_a_0结构体分别保存在属性int a 和 __Block_byref_a_0 *__forwarding中, 保存原始变量 指针 - 值,拿到变量a的地址,所以可以通过指针进行修改。 -
__block变量在Block函数中如何访问?
此时__main_block_func_0函数实现也变成如下状态:
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)++; //通过__forwarding指针访问a变量并可以修改
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_91163dcd57j_zw_xyry904bc0000gn_T_main_2b95a8_mi_0,((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), (int)((a->__forwarding->a))));
}
可以看到__main_block_func_0函数里面变量a的指针进行了拷贝,并且通过a->__forwarding->a访问变量a并进行修改,这样Block内部和外部指向同一个地址,所以可以修改外部变量。
Block的copy原理解析
什么时候进行Copy?
通过汇编调试可以找到Block进行copy的地方 。以下通过以下两步汇编断点调试,跳到_Block_copy的代码实现:
NSObject *obj = [[NSObject alloc] init];
void (^block1)(void) = ^{
// NSLog(@"a:%@",@(a));
NSLog(@"%@",obj);
};
block1();
根据提示可以看出Block源码出自libsystem_blocks.dylib动态库,Block的copy操作是在_Block_copy函数中完成的。
NSStackBlock变成NSMallocBlock过程演示
栈Block进行copy操作之后就变成堆Block的。接下来我们通过汇编调试验证这一过程。通过在_Block_copy调用前和调用结束打断点分析block类型变化来演示:
运行到断点位置,然后进入汇编调试,然后添加符号objc_retainBlock断点,继续运行:
这一步就可以看到已经准备调用_Block_copy函数了,在这之前打印Block发现是__NSStackBlock类型。接下来继续运行一步步跳入到_Block_copy实现,并在_Block_copy结束并准备返回(ret)前打断点,如下:
这回可以看到Block已经变成了__NSMallocBlock。
_Block_copy源码解析
由上面的调试可知,Block的copy发生在_Block_copy函数中,接下来通过源码查看_Block_copy的流程:
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; //如果是全局Block,则不需要copy,直接返回
}
else { // 如果是栈Block,则会申请一个堆空间创建一个新的Block,并将所有属性值拷贝一份
// 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.
//最后把block的类型改为堆Block
result->isa = _NSConcreteMallocBlock;
return result;
}
}
有源码可知,_Block_copy主要做了如下几件事情:
- 1、判断如果是重复copy,则直接返回:
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
当第一次copy之后BLOCK_NEEDS_FREE会被标记。下次在对同一个block进行copy时直接返回。
- 2、判断如果是全局Block则直接返回:
if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock; //如果是全局Block,则不需要copy,直接返回
}
全局Block是不需要copy的。
- 3、开辟堆空间,copy栈block到堆区
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.
//最后把block的类型改为堆Block
result->isa = _NSConcreteMallocBlock;
return result;
设置invoke:aBlock->invoke
更新Block的状态:flags
如果引用对象类型,copy对象:_Block_call_copy_helper(后面有专门对象copy的分析)
isa指针指向堆类型:result->isa = _NSConcreteMallocBlock
对象的Copy操作
对象的Copy是通过函数_Block_object_assign完成的。其源码如下:
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];
********/
// objc 指针地址 weakSelf (self)
_Block_retain_object(object);
// 持有
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// block 被一个 block 捕获
*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;
}
}
对象类型
对象类型有以下几种:
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
普通对象copy
普通的对象(不加任何修饰比如__weak, 或者__block)的copy会被Block持有的同时还被Block进行retain使得引用计数增加:
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
// objc 指针地址 weakSelf (self)
// arc
_Block_retain_object(object);
// 持有
*dest = object;
引用的对象本身是个Block
Block也有可能捕获其他的block,对于Block类型对象,直接调用_Block_copy(前面已有分析):
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// block 被一个 block 捕获
*dest = _Block_copy(object);
break;
__block变量的Copy
__block对象跟普通对象不一样,在block里面它是一个结构体,它的copy实现是在函数_Block_byref_copy中实现的:
static struct Block_byref *_Block_byref_copy(const void *arg) {
// Block_byref 结构体
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;
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;
}
Copy出来的forwarding和原来的forwarding指向同一个,保证了block内部持有的变量和外部的__block变量是同一个:
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
但是__block修饰的变量又可分为值变量类型(比如int类型等)和对象类型。对于对象类型,会调用对象的copy方法:
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));
}
其他对象的copy
其他对象没有copy,只是简单的指针赋值操作。
Block调用情况
Block是如何调用并执行里面的代码的呢?通过汇编断点继续调试:
实际上是在底层调用了Block_layout的invoke实现的。