前言
作为一个iOS开发工程师,对block
肯定不会陌生,它几乎是我们开发者使用的最多的类型,但是往往停留在怎么使用的层面,而对block
的底层实现原理却知之甚少。今天就对block
底层原理进行分析。
准备工作
block
基础语法
block
声明:
returnType (^ blockName)(params)
复制代码
block
赋值:
block = ^returnType(params){
};
复制代码
- 由于编译器可以从代码块的变量判断出block的返回值类型,所以返回值类型一般可以省略
一个返回值为int
参数为一个int
的block可以申明如下:
int (^xqBlock)(int) = ^(int a){
return a++;
};
复制代码
调用也很简单:
xqBlock(10);
复制代码
block
的类型
当 block
未捕获任何外部变量,或只使用了全局变量时,其类型是__NSGlobalBlock__(全局block)
int a = 8;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^xqBlock)(void) = ^{
NSLog(@"%d",a);
};
xqBlock();
NSLog(@"%@",xqBlock);
}
return 0;
}
**********************************打印结果**********************************
2022-02-23 15:08:37.618811+0800 KCBlockBuild[27580:431773] 8
2022-02-23 15:08:37.619485+0800 KCBlockBuild[27580:431773] <__NSGlobalBlock__: 0x100004030>
Program ended with exit code: 0
复制代码
- 仅使用了全局变量未捕获自动变量,为
__NSGlobalBlock__
当 block
捕获了外部变量未拷贝到堆区时为__NSStackBlock__(栈block)
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 8;
void (^__weak xqBlock)(void) = ^{
NSLog(@"%d",a);
};
xqBlock();
NSLog(@"%@",xqBlock);
}
return 0;
}
**********************************打印结果**********************************
2022-02-23 15:10:59.324554+0800 KCBlockBuild[27772:435681] 8
2022-02-23 15:10:59.325195+0800 KCBlockBuild[27772:435681] <__NSStackBlock__: 0x7ffeefbff398>
Program ended with exit code: 0
复制代码
- 变量
a
被xqBlock
捕获,但是由于使用__weak
修饰,不会自动拷贝到堆区
当 block
捕获了外部变量同时被拷贝到堆区时为__NSMallocBlock__(堆block)
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 8;
void (^ xqBlock)(void) = ^{
NSLog(@"%d",a);
};
xqBlock();
NSLog(@"%@",xqBlock);
}
return 0;
}
**********************************打印结果**********************************
2022-02-23 15:13:27.869170+0800 KCBlockBuild[27977:439050] 8
2022-02-23 15:13:27.869841+0800 KCBlockBuild[27977:439050] <__NSMallocBlock__: 0x109940b90>
Program ended with exit code: 0
复制代码
- 变量
a
被xqBlock
捕获, 此时会自动拷贝到堆区
我们已经知道block
是有类型的,那么block
是不是和我们普通的对象一样,也是一个类的实例呢?接下来,带着这个疑问将block
转换成id
类型,查看其结构如下图所示:
- 由结构可知,堆
block
实际是__NSMallocBlock__
类的实例对象,其继承链依次为__NSMallocBlock__
->__NSMallocBlock
->NSBlock
->NSObject
。 - 其它类型
block
与堆block
类似,这里就不一一列举了
被block捕获的变量的内存引用计数变化
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject* objc = [[NSObject alloc]init];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
void (^ xqBlock)(void) = ^{
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
xqBlock();
void(^__weak xqBlock1)(void) = ^{
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
xqBlock1();
void (^ xqBlock2)(void) = [xqBlock1 copy];
xqBlock2();
}
return 0;
}
**********************************打印结果**********************************
2022-02-23 15:33:30.209144+0800 KCBlockBuild[29465:464529] 1
2022-02-23 15:33:30.209642+0800 KCBlockBuild[29465:464529] 3
2022-02-23 15:33:30.209684+0800 KCBlockBuild[29465:464529] 4
2022-02-23 15:33:30.209708+0800 KCBlockBuild[29465:464529] 5
Program ended with exit code: 0
复制代码
- 对象
objc
初始化后引用计数为1
,当objc
被xqBlock
捕获后,引用计数变为2
,由于是__NSMallocBlock__
会自动拷贝到堆区,所以引用计数再加1变为3
xqBlock1
捕获了objc
引用计数加一变为4
,由于是__NSStackBlock__
,所以不会拷贝到堆区xqBlock2
是xqBlock1
拷贝到堆区,所以引用计数再加一变为5
通过上面的分析,可以知道被 block
捕获的变量被block
强引用了,如果一个对象对 block
强引用,同时,block
也捕获了这个对象就会造成循环引用
@interface XQPerson : NSObject
@property(nonatomic,copy)void (^xqBlock)(void);
@end
@implementation XQPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
XQPerson* person = [[XQPerson alloc]init];
person.xqBlock = ^{
NSLog(@"%@",person);
};
}
return 0;
}
复制代码
- 这个问题很容易解决,在block之前使用__weak声明一个变量,赋值为
weakPerson
指向person
,在block
内部使用weakPerson
即可,为了避免person
被提前释放导致block执行的时候person
为空,可以在block
内部使用一个__strong
声明一个局部变量对weakPerson
进行一次强引用。如下所示代码:
XQPerson* person = [[XQPerson alloc]init];
__weak typeof(person) weakPerson = person;
person.xqBlock = ^{
__strong typeof(weakPerson)strongPerson = weakPerson;
NSLog(@"%@",strongPerson);
};
复制代码
但是在使用 block
的时候,我们需要特别注意一个问题,如果在 block
内部再次使用 block
对 __strong
修饰的变量进行捕获,会造成该局部变量引用计数增加,依然会造成循环引用:
@interface XQPerson : NSObject
@property(nonatomic,copy)void (^xqBlock1)(void);
@end
@implementation XQPerson
@end
@interface ViewController ()
@property(nonatomic,copy)void (^xqBlock)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
XQPerson* person = [[XQPerson alloc]init];
__weak typeof(self) weakSelf = self;
self.xqBlock = ^{
__strong typeof(weakSelf)strongSelf = weakSelf;
person.xqBlock1 = ^{
NSLog(@"%@",strongSelf);
};
};
self.xqBlock();
person.xqBlock1();
}
@end
复制代码
person
被block
捕获, strongSelf
被person
的xqBlock1
捕获,相当于间接循环引用了,所以我们在使用block
的时候要特别注意block
的嵌套问题
源码分析
前面分析了一些block
的使用和对被捕获对象引用计数的影响,那么其底层原理有是什么呢?接下来,使用libclosure-79的源码和 clang
编译成C++
代码共同进行分析
首先定义一个堆block并加上断点后打开汇编调试,如下图所示:
运行后如下所示,会跳转执行objc_retainBlock
增加一个符号断点:objc_retainBlock
,继续调试
由此可以确定,在 block
被拷贝时会执行_Block_copy
函数
打开libclosure-79的源码,搜索_Block_copy
函数
在_Block_copy
函数中可以发现block
在底层的定义是Block_layout
结构体指针
struct Block_layout {
void * __ptrauth_objc_isa_pointer isa; // isa
volatile int32_t flags; // contains ref count //标志位
int32_t reserved; //预留参数
BlockInvokeFunction invoke; //指向block执行的函数指针
struct Block_descriptor_1 *descriptor;
// imported variables
};
#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; // 指向释放函数的函数指针,编译期赋值
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 { // 可选参数
// requires BLOCK_HAS_SIGNATURE
const char *signature; // block的签名例如 v8@?0
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
复制代码
flags
标志位解析
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime // 第0位表示是否正在释放
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime // 表示引用计数器的掩码
BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler
#endif
BLOCK_IS_NOESCAPE = (1 << 23), // compiler
BLOCK_NEEDS_FREE = (1 << 24), // runtime // 是否需要释放
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler // 是否有copy和dispose函数
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime // 是否有垃圾回收
BLOCK_IS_GLOBAL = (1 << 28), // compiler // 是否是全局block
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler // 是否有签名信息
};
复制代码
_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) { // 判断是否全局block
return aBlock;
}
else {// 栈 -> 堆 将栈block拷贝到堆区
// Its a stack block. Make a copy.
//获取block的大小
size_t size = Block_size(aBlock);
// 开辟size大小的内存空间
struct Block_layout *result = (struct Block_layout *)malloc(size);
if (!result) return NULL;
//将aBlock拷贝到result开辟的内存空间
memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#if __has_feature(ptrauth_signed_block_descriptors)
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
uintptr_t oldDesc = ptrauth_blend_discriminator(
&aBlock->descriptor,
_Block_descriptor_ptrauth_discriminator);
uintptr_t newDesc = ptrauth_blend_discriminator(
&result->descriptor,
_Block_descriptor_ptrauth_discriminator);
result->descriptor =
ptrauth_auth_and_resign(aBlock->descriptor,
ptrauth_key_asda, oldDesc,
ptrauth_key_asda, newDesc);
}
#endif
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
//将引用计数设置为1,同时标记需要释放
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// 获取copy函数并执行
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
// 修改isa指向 为堆block
result->isa = _NSConcreteMallocBlock;
return result;
}
}
复制代码
if (aBlock->flags & BLOCK_NEEDS_FREE)
判断是否需要释放if (aBlock->flags & BLOCK_IS_GLOBAL)
判断是否为全局block ,如果是则直接返回aBlock
size_t size = Block_size(aBlock);
获取block
大小struct Block_layout *result = (struct Block_layout *)malloc(size);
在堆区开辟size
大小的内存空间并赋值给resultmemmove(result, aBlock, size);
将aBlock
拷贝到result
的内存空间result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
重置引用计数result->flags |= BLOCK_NEEDS_FREE | 2
将引用计数设置为1
(第1位开始表示引用计数) ,同时标记需要释放_Block_call_copy_helper(result, aBlock);
获取copy
函数指针并执行,copy
是可选参数,当block
是全局block
或捕获的变量是基本数据类型
(例如int)时,copy
为空result->isa = _NSConcreteMallocBlock;
修改result
类型为堆block
_Block_call_dispose_helper
函数分析:
static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
// 获取copy函数
if (auto *pFn = _Block_get_dispose_function(aBlock))
pFn(aBlock);// 如果有copy函数就执行
}
复制代码
_Block_get_copy_function
函数分析:
static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_function(struct Block_layout *aBlock)
{
if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE))
return NULL;
获取block的descriptor1首地址
void *desc = _Block_get_descriptor(aBlock);
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
struct Block_descriptor_small *bds =
(struct Block_descriptor_small *)desc;
return _Block_get_relative_function_pointer(
bds->copy, void (*)(void *, const void *));
}
#endif
// 内存平移得到descriptor_2
struct Block_descriptor_2 *bd2 =
(struct Block_descriptor_2 *)((unsigned char *)desc +
sizeof(struct Block_descriptor_1));
// 得到copy
return _Block_get_copy_fn(bd2);
}
static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_fn(struct Block_descriptor_2 *desc)
{
return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy);
}
#define _Block_get_function_pointer(field) \
(field)
复制代码
lldb
调试验证block
结构:
void (^ xqBlock)(void) = ^{
NSLog(@"xq");
};
id globalBlock = xqBlock;
int a = 18;
void (^ xqBlock1)(void) = ^{
NSLog(@"%d",a);
};
id intBlock = xqBlock1;
NSObject *obj = [[NSObject alloc]init];
void (^ xqBlock2)(void) = ^{
NSLog(@"%@",obj);
};
id objcBlock = xqBlock2;
*****************************lldb调试*****************************
(lldb) po globalBlock
<__NSGlobalBlock__: 0x100004040>
signature: "v8@?0"
invoke : 0x100003de0 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__main_block_invoke)
(lldb) po intBlock
<__NSMallocBlock__: 0x109941570>
signature: "v8@?0"
invoke : 0x100003e10 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__main_block_invoke_2)
(lldb) po objcBlock
<__NSMallocBlock__: 0x1099415a0>
signature: "v8@?0"
invoke : 0x100003e40 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__main_block_invoke_3)
copy : 0x100003e70 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__copy_helper_block_e8_32s)
dispose : 0x100003eb0 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__destroy_helper_block_e8_32s)
复制代码
- 由调试结果可知,对于未捕获变量或只捕获了基本数据类型的block不存在
copy
和dispose
,只有引入了对象类型才存在copy
和dispose
源码调试:
声明如下block:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc]init];
void (^ xqBlock)(void) = ^{
NSLog(@"%@",obj);
};
NSLog(@"block is %@", xqBlock);
}
return 0;
}
复制代码
在_Block_copy
函数打断点调试如下:
(lldb) x/4g aBlock // 输出aBlock内存信息
0x7ffeefbff398: 0x0000000100394060 0x00000000c2000000
0x7ffeefbff3a8: 0x0000000100003e50 0x0000000100004020
(lldb) po 0x0000000100394060 // 输出isa
__NSStackBlock__
// 输出flags
(lldb) p/t 0xc2000000
// 由flags结果可以看出 包含 1<<25 即有copy和dispose
(unsigned int) $2 = 0b11000010000000000000000000000000
//输出block的执行函数invoke
(lldb) p (BlockInvokeFunction)0x0000000100003e50
(BlockInvokeFunction) $3 = 0x0000000100003e50 (KCBlockBuild`__main_block_invoke at main.m:30)
// 类型强转
(lldb) p (Block_descriptor_1*)0x0000000100004020
(Block_descriptor_1 *) $4 = 0x0000000100004020
//取值 得到 reserved = 0, size = 40
(lldb) p *$4
(Block_descriptor_1) $5 = (reserved = 0, size = 40)
// 内存平移得到 descriptor_2
(lldb) p (Block_descriptor_2 *)($4 + 1)
(Block_descriptor_2 *) $6 = 0x0000000100004030
(lldb) p *$6
// 取值,得到copy和dispose
(Block_descriptor_2) $7 = {
copy = 0x0000000100003e80 (KCBlockBuild`__copy_helper_block_e8_32s at main.m)
dispose = 0x0000000100003ec0 (KCBlockBuild`__destroy_helper_block_e8_32s at main.m)
}
// 内存平移得到 descriptor_3
(lldb) p (Block_descriptor_3 *)($6 + 1)
(Block_descriptor_3 *) $8 = 0x0000000100004040
(lldb) p *$8
取值得到 签名和layout
(Block_descriptor_3) $9 = (signature = "v8@?0", layout = "")
复制代码
图解:
通过上面的分析,已经知道了 block
的底层结果,但是 block
内部的 copy
,dispose
等函数什么时候赋值,以及这些函数到底做了什么,我们还是一无所知的,由于copy
和dispose
等函数是在编译期
决定的,我们可以将block编译成C++代码进行分析。
声明如下一个block:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [NSObject new];
void (^ xqBlock)(void) = ^{
NSLog(@"%@",obj);
};
xqBlock();
NSLog(@"block is %@", xqBlock);
}
return 0;
}
复制代码
经过clang -rewrite-objc main.m -o main.cpp
编译生成c++代码,main
函数如下
//删除部分类型转换代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = objc_msgSend)(objc_getClass("NSObject"), sel_registerName("new"));
xqBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
xqBlock->FuncPtr(xqBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_4d_29qjz7cx0vs3nqw8qm0_ypz0jm96y7_T_main_bff74a_mi_1, xqBlock);
}
return 0;
}
复制代码
- 通过转换出的
C++
代码,可以分析出xqBlock
初始化成__main_block_impl_0
的结构体指针,参数依次是__main_block_func_0
,__main_block_desc_0_DATA
,obj
,570425344
block
的调用实际是调用FuncPtr
并将block
自身传入
__main_block_impl_0
结构体分析:
struct __main_block_impl_0 {
struct __block_impl impl;//包含isa,,flags, func等信息
struct __main_block_desc_0* Desc;// des信息,包含
NSObject *__strong obj;// 捕获的成员
//构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) {// obj = _obj
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
- 包含了block的所有信息,
isa
(运行时确定实际类型),flags
,FuncPtr
,Desc
,以及捕获的变量,通过main
函数里调用构造函数进行了初始化
__block_impl
结构体分析:
struct __block_impl {
void *isa;
int Flags; // 标识
int Reserved; // 预留参数
void *FuncPtr; // 执行函数
};
复制代码
- 与libclosure-79源码中
Block_layout
结构几乎一样,只是少了descriptor
,编译出的源码中,descriptor
信息都被保存在了__main_block_desc_0
结构体中 FuncPtr
被赋值为__main_block_func_0
函数
__main_block_func_0
函数分析:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *__strong obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_4d_29qjz7cx0vs3nqw8qm0_ypz0jm96y7_T_main_5ed05e_mi_0,obj);
}
复制代码
block
的执行函数,可以看到,在此函数内,仅仅将一个新的指针指向了obj
,并没有对地址进行拷贝,所以在block
内部不能对捕获的变量进行赋值。
__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};
复制代码
__main_block_desc_0_DATA
对结构体进行初始化,参数依次是0
,__main_block_impl_0
结构体的大小__main_block_copy_0
函数,即为copy
函数,__main_block_dispose_0
函数即为disopse
函数
__main_block_copy_0
和__main_block_dispose_0
函数分析:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src){
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
复制代码
- 这两个函数分别调用了libclosure-79源码的
_Block_object_assign
函数和_Block_object_dispose
函数
_Block_object_assign
函数分析:
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对象
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
// __block
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
// __weak修饰,只在__block对象使用
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
// __block对象中成员变量copy,dispose时使用
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
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];
********/
// _Block_retain_object_default = fn (arc)
_Block_retain_object(object);
*dest = object;
break;
//block对象
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
// __block或 __weak __block 修饰的对象
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;
// __block 修饰的对象是结构体类型,其中也有 copy函数 会调用此处,后续进行分析
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;
__block 或__weak __block 修饰的对象是结构体类型,其中也有 copy函数 会调用此处
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_FIELD_IS_OBJECT
,普通对象或将block
转为id
的对象被捕获block
后,copy
函数会调用此分支,调用_Block_retain_object
函数会调用到libobjc.A.dylib
的objc_retain
,使对象引用计数+1
BLOCK_FIELD_IS_BLOCK
,block
对象被捕获后执行此分支 调用_Block_copy
函数BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK
或者BLOCK_FIELD_IS_BYREF
,__block
或__weak __block
修饰的变量被block
捕获后copy
函数执行此分支,后续分析_Block_byref_copy
函数- 另外两个分支都是针对
__block
修饰的变量,在底层生成的结构体中的成员,进行拷贝时会执行此分支,进行指针赋值
_Block_retain_object
函数解析:
_Block_release_object
是一个全局函数指针,可能在运行起来后进行赋值,所以在程序运行起来后,我们可以将其打印出来,如下图所示:
因此,我们可以确定,该函数是libobjc.A.dylib
框架下的objc_retain
函数,源码如下:
id
objc_retain(id obj)
{
if (obj->isTaggedPointerOrNil()) return obj;
return obj->retain();
}
复制代码
- 由查看源码可以确定
_Block_retain_object
实际是对捕获的变量进行了一次retain
__block
修饰变量的分析:
由以上的分析可以看出,__block
修饰的变量和普通的变量会执行不同的分支,为什么会有这样的差异呢,接下来对__block
进行分析
定义一个 __block
变量并在block中使用此变量:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSObject* objc = [NSObject alloc];
void(^xqBlock)(void) = ^{
NSLog(@"%@",objc);
};
xqBlock();
}
return 0;
}
复制代码
编译成C++文件如下:
// 省略部分类型强转
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__Block_byref_objc_0 objc = {0,&objc, 33554432, sizeof(__Block_byref_objc_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc"))};
xqBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &objc, 570425344));
xqBlock->FuncPtr(xqBlock);
}
return 0;
}
复制代码
- 由编译成的C++代码可以看出,
objc
被转换成了一个__Block_byref_objc_0
结构体,传入的参数依次为0
,objc的地址
,__Block_byref_id_object_copy_131
函数,__Block_byref_id_object_dispose_131
函数。 block
初始化将objc
的地址传入并捕获。
__Block_byref_objc_0
结构体分析:
struct __Block_byref_objc_0 {
void *__isa; // isa
__Block_byref_objc_0 *__forwarding; // 指向自身的地址
int __flags; // 标志位,表示是否有copy,dispose函数等,
int __size; // 结构体大小
void (*__Block_byref_id_object_copy)(void*, void*); // 结构体对objc的copy函数,捕获的变量是基础数据类型时没有
void (*__Block_byref_id_object_dispose)(void*); // 结构体对objc的释放函数,捕获的变量是基础数据类型时没有
NSObject *__strong objc; // 捕获的变量
};
复制代码
我们再来看看此时的__main_block_copy_0
函数(block
的copy
函数):
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
复制代码
- 此时的参数发生了变化,第三个参数为8即为,所以此时执行
_Block_object_assign
函数会执行BLOCK_FIELD_IS_BYREF
分支,执行_Block_byref_copy
函数
_Block_byref_copy
函数分析:
分析_Block_byref_copy
函数前,先熟悉一下__block
修饰的变量在底层的结构 Block_byref
结构体
// 结构体
struct Block_byref {
void * __ptrauth_objc_isa_pointer isa; //
struct Block_byref *forwarding; // 指向自身
volatile int32_t flags; // contains ref count //标识
uint32_t size; // 大小
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; //= __Block_byref_id_object_copy_131 ,copy函数
BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131 dispose函数
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout; // 捕获的变量
};
复制代码
_Block_byref_copy
函数:
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
// __block修饰时 src->forwarding->flags = BLOCK_BYREF_HAS_COPY_DISPOSE | BLOCK_BYREF_LAYOUT_EXTENDED | 1 << 29
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
// 修改flags,下次不再拷贝到堆区
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
//内存平移得到Block_byref_2结构体指针 并拷贝
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) {
//内存平移得到Block_byref_3结构体指针 并拷贝
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
,并将src
的值赋值给copy
src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE
判断是否有Block_byref_2
结构体,如果有将src
和copy
都平移Block_byref
结构体的大小到Block_byref_2
,分别得到src2
和copy2
,并将src2
的byref_keep
和byref_destroy
函数赋值给copy2
。src->flags & BLOCK_BYREF_LAYOUT_EXTENDED
判断是否有layout
(即为成员),如果有,将src2
和copy2
都平移Block_byref_2
结构体的大小到Block_byref_3
,分别得到src3
和copy3
,并将src3
的layout
赋值给copy3
(*src2->byref_keep)(copy, src)
,调用byref_keep
函数,对捕获的变量进行保存。
上文已经介绍过 __block
修饰的变量clang
编译后是一个__Block_byref_objc_0
结构体。
再次回顾一下__Block_byref_objc_0
结构体
struct __Block_byref_objc_0 {
void *__isa;
__Block_byref_objc_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *objc;
};
复制代码
此时在_Block_byref_copy
函数中用到的byref_keep
。即为__Block_byref_id_object_copy
,我们再回到clang
编译后的代码寻找这个函数,可以找到,__Block_byref_id_object_copy
如下:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
复制代码
dst
和src
为_Block_byref_copy
函数中传入的copy
和src
,此时平移40
个字节,刚好平移到 加入进来的成员变量objc
131
=128 | 3
=BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT
,当再次执行到_Block_object_assign
函数时,进入BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT
分支,对objc
进行了指针拷贝,所以使用__block
修饰的变量,在block
内部可以进行修改
小结: __block
修饰的变量在底层实际是Block_byref
的结构体,在底层会进行三重拷贝,依次为:_Block_copy
->_Block_object_assign
->_Block_byref_copy
->_Block_object_assign
,分别为将block
拷贝到堆区,将__block变量
拷贝到堆区,指针拷贝。
block的释放:
block
的释放函数是_Block_release
,在源码中可以看到代码如下:
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
free(aBlock);
}
}
复制代码
_Block_call_dispose_helper
执行block的dispose
函数_Block_destructInstance
析构block
dispose
函数分析:
上文分析copy
函数时已经提到过dispose
函数在编译时被赋值为__main_block_dispose_0
,编译成c++
代码后如下所示:
//捕获普通对象
static void __main_block_dispose_0(struct __main_block_impl_0*src){
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
// 捕获block
static void __main_block_dispose_1(struct __main_block_impl_1*src) {
_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}
//捕获__block对象
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
复制代码
- 通过编译后的函数调用,可以看到,都是调用
_Block_object_dispose
函数,只是flags
参数因捕获的变量类型不同而有所差异。
_Block_object_dispose
函数分析:
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
复制代码
_Block_object_dispose
和上文分析的_Block_object_assign
函数非常相似,只是这里处理的是相反的逻辑。__block
修饰的变量执行_Block_byref_release
函数,调用结构体内的dispose函数,实际也会调用到这里,进入最后一个分支,实际什么也没做,然后对结构体进行释放。- 如果捕获的是
block
对象,再次执行_Block_release_object
,对捕获的block
对象进行释放。 - 捕获的是普通对象执行
_Block_release_object
函数,对捕获的对象进行release
。
_Block_byref_release
函数分析:
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
复制代码
_Block_byref_release
函数逻辑相对简单,仅仅通过内存平移取出byref2
,调用byref_destroy
函数,最终会调用_Block_object_dispose
函数。由于传参为131 = BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT
,所以实际这个函数什么也不会做。然后释放byref
_Block_release_object
函数分析
_Block_release_object
是一个全局函数指针,可能在运行起来后进行赋值,所以在程序运行起来后,我们可以将其打印出来,如下图所示:
因此,我们可以确定,该函数是libobjc.A.dylib
框架下的objc_release
函数,源码如下:
void
objc_release(id obj)
{
if (obj->isTaggedPointerOrNil()) return;
return obj->release();
}
复制代码
所以可以得出_Block_release_object
函数是对捕获的对象进行了一次release
_Block_destructInstance
分析:
和上面的_Block_release_object
一样,_Block_destructInstance
也是一个全局函数指针,我们可以使用同样的方式将其输出:
因此,我们可以确定,该函数是libobjc.A.dylib
框架下的objc_destructInstance
函数,源码如下:
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
复制代码
- 查看源码,可以确定,
_Block_destructInstance
实际是调用objc_destructInstance
函数对block
进行释放
总结
block
在底层实际也是一个对象,按是否捕获变量和是否拷贝到堆区分为__NSGlobalBlock__
,__NSStackBlock__
和__NSMallocBlock__
三种类型。又根据捕获变量类型的不同,决定block
内部的结构,如当捕获的类型为基本数据类型
,则block
没有copy
和dispose
函数。如果是普通对象,调用copy
函数对对象进行一次retain
,如果是 block
对象,则会调用_Block_copy
对block
进行拷贝,如果是__block
修饰的基本数据类型
,则底层生成的结构体不含有byref_keep
和byref_destroy
函数,如果是__block
修饰是对象
,则底层生成的结构体含有byref_keep
和byref_destroy
函数,实际是进行了三重拷贝