一、Block类型
我们平时在开发中使用的到的Block主要有三种类型:
-
__NSGlobalBlock__
:全局block,没有参数,也没有返回值的block; -
__NSMallocBlock__
:堆block,既是函数,也是对象,对外部变量执行了拷贝; -
__NSStackBlock__
:栈block,未对外部变量拷贝,也可以通过__weak
修饰block;
下面通过一些代码的调试来观察这几种Block
1.1 全局block:NSGlobalBlock
- (void)testGlobalBlock {
// __NSGlobalBlock__: 全局block 没有参数 也没有返回值
void (^globalBlock)(void) = ^{
NSLog(@"testGlobalBlock");
};
NSLog(@"globalBlock %@", (globalBlock));
}

1.2 堆block:NSMallocBlock
- (void)testMallocBlock {
// __NSMallocBlock__: 堆block 即是函数 也是对象
// 对外部变量a进行了拷贝, 所以是堆block
int a = 10;
void (^mallocBlock1)(void) = ^{
NSLog(@"mallocBlock1 : %d", a);
};
NSLog(@"mallocBlock1 %@", mallocBlock1);
NSLog(@"mallocBlock2 %@", ^{
NSLog(@"mallocBlock2 : %d", a);
});
}

1.3 栈block:NSStackBlock
- (void)testStackBlock {
// __NSStackBlock__: 在a没有拷贝前, 是栈block
// 也可以显示的使用 __weak 表示 stackBlock1 对右边的block不持有, 这个时候右边的block就还是栈block
int a = 10;
void (^__weak stackBlock1)(void) = ^{
NSLog(@"stackBlock1 : %d", a);
};
NSLog(@"stackBlock1 %@", stackBlock1);
}

总结
- block是存储在全局区的
- 如果block访问外界变量, 且对外部变量进行了copy, 或直接对block进行了copy
- 如果此时的block是强引用, 则block存储在堆区
- 如果此时的block是弱引用, 则block存储在栈区
Block循环引用
循环引用造成的条件就是互相持有,或者是在某条链路上是存在环形持有关系。例如:
self->block->self
self->view->model->block->self
对于解决循环引用也是非常高频的操作,有以下四种方法:
weak-strong-dance
-
__block
修饰对象, 在内部手动将block变量置为nil
- 通过将需要使用的对象放到
block的参数
中传递到block内部 - 使用
NSProxy
传递消息
前三种都是非常常规的处理手段,这里就不做介绍了。重点介绍下第4种,怎么用NSProxy
1.4 自定义NSProxy

从上面类的定义中可以看出,它并没有继承NSObject
,只是实现了<NSObject>
的部分协议,那它有什么用处呢?可以实现伪多继承
我们继承这个类来实现一个自定义的类XYProxy
XYProxy.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface XYProxy : NSProxy
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(NSObject *)objc;
@end
NS_ASSUME_NONNULL_END
XYProxy.m
#import "XYProxy.h"
@interface XYProxy ()
@property (nonatomic, weak, readonly) NSObject *objc;
@end
@implementation XYProxy
- (id)transformObjc:(NSObject *)objc {
_objc = objc;
return self;
}
+ (instancetype)proxyWithObjc:(NSObject *)objc {
return [[self alloc] transformObjc:objc];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = [invocation selector];
if ([self.objc respondsToSelector:sel]) {
[invocation invokeWithTarget:self.objc];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSMethodSignature *signature = [super methodSignatureForSelector:sel];
if (self.objc) {
signature = [self.objc methodSignatureForSelector:sel];
}
return signature;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [self.objc respondsToSelector:aSelector];
}
@end
对消息的查找和转发协议比较熟悉的话,对这里的API应该是非常了解的。这里实现的是转发的最后一步——慢速转发流程
。为什么是最后一步,不是动态决议也不是快速转发流程呢?NSProxy
的作用就是通过消息的转发来实现某种功能,它本身的作用类似于代理,去帮助别人实现某种功能(调某个方法),同时需要转发消息的调用者和方法
,只有慢速转发流程才满足。
1.5 NSProxy
总结
可以使用 NSProxy 实现伪多继承
- NSProxy 是和NSObject同级别的类, 可以当做一个虚拟类, 只实现了NSObject的协议. 从它的定义可以看出它并没有集成某个父类
- NSProxy 可以用来做消息重定向封装的一个抽象类. 在需要代理人, 中间件的时候可以使用
- 重写消息转发流程里面的慢速转发流程中的两个方法 可以实现消息的重定向
NSProxy 的使用场景
- 实现多继承的功能. 通过自定义的Proxy类代理target, 进行消息的转发
- 解决
NSTimer/CADisplayLink
创建时对target的强引用
问题, 可以参照YYKit中的YYWeakProxy
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[XYProxy proxyWithObjc:self] selector:@selector(countTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
二、Block本质
上面介绍NSProxy只是为解决循环引用提供一个新的思路,本文的核心仍然是Block。上面借助LLDB查看了block的类型,但并不知道block在底层是怎么运行的,分析block,同样可以通过汇编、源码、CPP分析,这里我们先objc的block转成cpp文件,查看block的结构。
- 文件:
XYBlockClang.c
void clangBlockFunc() {
void(^srBlock)(int) = ^(int blockNum){
printf("clangBlockFunc : %d", blockNum);
};
printf("------");
srBlock(110);
}
- 命令:
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc XYBlockClang.c
struct __clangBlockFunc_block_impl_0 {
struct __block_impl impl;
struct __clangBlockFunc_block_desc_0* Desc;
__clangBlockFunc_block_impl_0(void *fp, struct __clangBlockFunc_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __clangBlockFunc_block_func_0(struct __clangBlockFunc_block_impl_0 *__cself, int blockNum) {
printf("clangBlockFunc : %d", blockNum);
}
static struct __clangBlockFunc_block_desc_0 {
size_t reserved;
size_t Block_size;
} __clangBlockFunc_block_desc_0_DATA = { 0, sizeof(struct __clangBlockFunc_block_impl_0)};
void clangBlockFunc() {
void(*srBlock)(int) = ((void (*)(int))&__clangBlockFunc_block_impl_0((void *)__clangBlockFunc_block_func_0, &__clangBlockFunc_block_desc_0_DATA));
printf("------");
((void (*)(__block_impl *, int))((__block_impl *)srBlock)->FuncPtr)((__block_impl *)srBlock, 110);
}
其他的内容已经省略,将我们写的代码转成的cpp代码拿出来分析。注意这里使用的c文件转的cpp文件,对比objc文件,只是少了个类名的前缀,其他结构都是完全一致。后面会贴上objc转成的cpp文件的代码,这里可以先进行分析。
2.1 block结构体
- 文件名:
XYBlockClang.c
- 函数名:
void clangBlockFunc()
struct __clangBlockFunc_block_impl_0 {
struct __block_impl impl;
struct __clangBlockFunc_block_desc_0* Desc;
__clangBlockFunc_block_impl_0(void *fp, struct __clangBlockFunc_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- block名称的生成:
__函数名__block_impl_index
- 第一个属性:
__block_impl
,从源码中找到__block_impl
类型的定义
#ifndef BLOCK_IMPL
#define BLOCK_IMPL
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
-
isa
:类型指针,作为iOS开发,这个就不用多说了,不了解的可以看这篇文章02--对象本质02--isa本质; -
Flags
:标记,从字面意思上可以看出是一个标记,但用来标记啥的,我们后面再做分析; -
Reserved
:字面意思是保留的意思,可能是作为将来的某个扩展字段来使用的,具体有啥用,可以在后面遇到了再做分析; -
FuncPtr
:函数指针,很多时候我们说block的本质是函数指针,其实就来源于此;
- 第二个属性:
__clangBlockFunc_block_desc_0
同样可以从源码中找到相应的定义,我们知道NSObject都有一个- (NSString *)description
方法,同样的block也有自己的desc
static struct __clangBlockFunc_block_desc_0 {
size_t reserved;
size_t Block_size;
} __clangBlockFunc_block_desc_0_DATA = { 0, sizeof(struct __clangBlockFunc_block_impl_0)};
-
reserved
:保留字段,后面遇到再做分析 -
Block_size
:block内存大小 - 默认值:
{ 0, sizeof(struct __clangBlockFunc_block_impl_0)};
,
- 构造函数:
__clangBlockFunc_block_impl_0
__clangBlockFunc_block_impl_0(void *fp, struct __clangBlockFunc_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
- clang将这个block编译成了栈函数:
_NSConcreteStackBlock
- Flags参数的赋值,默认传了个0(这个数字可能跟block列表有关)
- FuncPtr函数指针的赋值
- desc的赋值
最后可以知道block的构造函数只接受两个参数(void *fp, struct __clangBlockFunc_block_desc_0 *desc)
2.2 block的实现
代码中的block定义为:
void(^srBlock)(int) = ^(int blockNum){
printf("clangBlockFunc : %d", blockNum);
};
在源码中表现为:
static void __clangBlockFunc_block_func_0(struct __clangBlockFunc_block_impl_0 *__cself, int blockNum) {
printf("clangBlockFunc : %d", blockNum);
}
- 首先,clang将其编译成了一个
static
函数 -
void
:对应srBlock
的void -
srBlock
:重写成了__clangBlockFunc_block_func_0
- 参数列表:
(self, ...)
,第一个参数是默认的self
,后面的参数才是srBlock
中声明的参数列表 -
函数实现
:因为没有其他代码,这里看到的是相同的一段printf代码。后面添加了值捕获以及拷贝相关的代码再做分析
对比上面两个命名,第一个是block结构体的声明,用的是...impl...
,后面的事block的实现,用的是...func...
,所以我们称block的本质是函数指针确实说得过去。
2.3 block的声明
-
XYBlockClang.c
文件
void clangBlockFunc() {
void(^srBlock)(int) = ^(int blockNum){
printf("clangBlockFunc : %d", blockNum);
};
printf("------");
srBlock(110);
}
XYBlockClang.cpp
void clangBlockFunc() {
void(*srBlock)(int) = ((void (*)(int))&__clangBlockFunc_block_impl_0((void *)__clangBlockFunc_block_func_0, &__clangBlockFunc_block_desc_0_DATA));
printf("------");
((void (*)(__block_impl *, int))((__block_impl *)srBlock)->FuncPtr)((__block_impl *)srBlock, 110);
}
-
srBlock
的声明:void(*srBlock)(int)
,确实有点像函数指针😂
(void (*)(int))
&__clangBlockFunc_block_impl_0(
(void *)__clangBlockFunc_block_func_0,
&__clangBlockFunc_block_desc_0_DATA)
);
- 第一行:表示block的签名,可以看成是一个函数的签名;
- 第二行:取block的地址,可以保证
*srBlock
能访问到block结构体里面的属性; - 第三行:传递的是
__clangBlockFunc_block_func_0
函数实现,保存到*srBlock
的FuncPtr
里面; - 第四行:就是我们上面分析的block的描述信息
2.4 block的调用
(
(void (*)(__block_impl *, int))
((__block_impl *)srBlock)->FuncPtr
)
((__block_impl *)srBlock, 110);
- 第一行:
(void (*)(__block_impl *, int))
强转成srBlock函数指针的类型,这样才可以解析后面的参数列表; - 第二行:
((__block_impl *)srBlock)->FuncPtr
调用srBlock的函数指针; - 第三行:
((__block_impl *)srBlock, 110)
,第一个参数:block本身,第二个参数:block声明的int参数;
到这里,我们对一个基本block结构体、block类型的定义、block的调用有了一个非常详细的了解。但我们仍然不知道为什么block中使用self会造成循环引用,后面会进行详细的讲解。
2.5 block在objc中的编译
-
XYBlockObjc.m
文件
@implementation XYBlockObjc
- (void)clangBlockFunc {
void(^srBlock)(int) = ^(int blockNum){
printf("clangBlockFunc : %d", blockNum);
};
printf("------");
srBlock(110);
}
@end
-
XYBlockObjc.cpp
文件
struct __XYBlockObjc__clangBlockFunc_block_impl_0 {
struct __block_impl impl;
struct __XYBlockObjc__clangBlockFunc_block_desc_0* Desc;
__XYBlockObjc__clangBlockFunc_block_impl_0(void *fp, struct __XYBlockObjc__clangBlockFunc_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __XYBlockObjc__clangBlockFunc_block_func_0(struct __XYBlockObjc__clangBlockFunc_block_impl_0 *__cself, int blockNum) {
printf("clangBlockFunc : %d", blockNum);
}
static struct __XYBlockObjc__clangBlockFunc_block_desc_0 {
size_t reserved;
size_t Block_size;
} __XYBlockObjc__clangBlockFunc_block_desc_0_DATA = { 0, sizeof(struct __XYBlockObjc__clangBlockFunc_block_impl_0)};
static void _I_XYBlockObjc_clangBlockFunc(XYBlockObjc * self, SEL _cmd) {
void(*srBlock)(int) = ((void (*)(int))&__XYBlockObjc__clangBlockFunc_block_impl_0((void *)__XYBlockObjc__clangBlockFunc_block_func_0, &__XYBlockObjc__clangBlockFunc_block_desc_0_DATA));
printf("------");
((void (*)(__block_impl *, int))((__block_impl *)srBlock)->FuncPtr)((__block_impl *)srBlock, 110);
}
我们对比c转成的cpp文件来看,除了在名称前面添加了类名,其他的基本上没有什么差别,但是在编译出来的源文件中会有非常多的objc的类型。如果是单独分析某个关键字的特性,我们完全可以借用c文件,而不用去被其他类型干扰。
三、Block的值捕获
在block的使用中,我们遇到的最多的场景就是对外部变量的捕获,而遇到最多的问题就是block对self的循环引用,上面的分析并不能让我们对这些场景有个深入的了解,我们需要深入去研究block对外部变量的捕获机制。先来看一段代码:
-
XYBlockValueCapture.m
文件,部分代码,全局变量通过懒加载的方式赋值
typedef int(^XYTestBlock)(int code);
@interface XYBlockValueCapture ()
@property (nonatomic, assign) int globalNum;
@property (nonatomic, strong) NSObject *globalObj;
@property (nonatomic, copy) NSString *globalStr;
@end
- (void)testBlockFunc {
int localNum = 10;
NSObject *localObj = [NSObject new];
NSString *localStr = @"local str";
XYTestBlock testBlock = ^(int code) {
NSLog(@"local variable >> num : %d >> obj : %@ >> str : %@", localNum, localObj, localStr);
NSLog(@"global variable >> num : %d >> obj : %@ >> str : %@", self.globalNum, self.globalObj, self.globalStr);
return 0;
};
NSLog(@"block return : %d", testBlock(110));
}
-
XYBlockValueCapture.cpp
文件,部分代码
struct __XYBlockValueCapture__testBlockFunc_block_impl_0 {
struct __block_impl impl;
struct __XYBlockValueCapture__testBlockFunc_block_desc_0* Desc;
int localNum;
NSObject *localObj;
NSString *localStr;
XYBlockValueCapture *self;
__XYBlockValueCapture__testBlockFunc_block_impl_0(void *fp, struct __XYBlockValueCapture__testBlockFunc_block_desc_0 *desc, int _localNum, NSObject *_localObj, NSString *_localStr, XYBlockValueCapture *_self, int flags=0) : localNum(_localNum), localObj(_localObj), localStr(_localStr), self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __XYBlockValueCapture__testBlockFunc_block_func_0(struct __XYBlockValueCapture__testBlockFunc_block_impl_0 *__cself, int code) {
int localNum = __cself->localNum; // bound by copy
NSObject *localObj = __cself->localObj; // bound by copy
NSString *localStr = __cself->localStr; // bound by copy
XYBlockValueCapture *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_30a962_mi_1, localNum, localObj, localStr);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_30a962_mi_2, ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("globalNum")), ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("globalObj")), ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("globalStr")));
return 0;
}
static void __XYBlockValueCapture__testBlockFunc_block_copy_0(struct __XYBlockValueCapture__testBlockFunc_block_impl_0*dst, struct __XYBlockValueCapture__testBlockFunc_block_impl_0*src) {_Block_object_assign((void*)&dst->localObj, (void*)src->localObj, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->localStr, (void*)src->localStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __XYBlockValueCapture__testBlockFunc_block_dispose_0(struct __XYBlockValueCapture__testBlockFunc_block_impl_0*src) {_Block_object_dispose((void*)src->localObj, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->localStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __XYBlockValueCapture__testBlockFunc_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __XYBlockValueCapture__testBlockFunc_block_impl_0*, struct __XYBlockValueCapture__testBlockFunc_block_impl_0*);
void (*dispose)(struct __XYBlockValueCapture__testBlockFunc_block_impl_0*);
} __XYBlockValueCapture__testBlockFunc_block_desc_0_DATA = { 0, sizeof(struct __XYBlockValueCapture__testBlockFunc_block_impl_0), __XYBlockValueCapture__testBlockFunc_block_copy_0, __XYBlockValueCapture__testBlockFunc_block_dispose_0};
static void _I_XYBlockValueCapture_testBlockFunc(XYBlockValueCapture * self, SEL _cmd) {
int localNum = 10;
NSObject *localObj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
NSString *localStr = (NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_30a962_mi_0;
XYTestBlock testBlock = ((int (*)(int))&__XYBlockValueCapture__testBlockFunc_block_impl_0((void *)__XYBlockValueCapture__testBlockFunc_block_func_0, &__XYBlockValueCapture__testBlockFunc_block_desc_0_DATA, localNum, localObj, localStr, self, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_30a962_mi_3, ((int (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 110));
}
对于上面的代码,我们同样是通过block的声明
、block的实现
、block的调用
三个步骤来分析。关于block的基础类型,我们上面做过详细分析,这里就不再分析,这里只分析与变量相关的代码。
3.1 局部变量捕获
block的声明
上面的例子中,我们定义了三个局部变量,它们是怎么被block使用的呢?
int localNum = 10;
NSObject *localObj = [NSObject new];
NSString *localStr = @"local str";
同样的,clang将block结构体定义为:__XYBlockValueCapture__testBlockFunc_block_impl_0
,在这个结构体中,除了我们上面讲到的两个属性和一个构造方法之外,还增加了四个属性,其中有三个就是我们定义的三个变量,还有一个是self
,self
的内容我们放到后面讲,本节只关注这三个局部变量即可。
int localNum;
NSObject *localObj;
NSString *localStr;
XYBlockValueCapture *self;
构造函数的变更:
__XYBlockValueCapture__testBlockFunc_block_impl_0(
void *fp, struct __XYBlockValueCapture__testBlockFunc_block_desc_0 *desc,
int _localNum, NSObject *_localObj, NSString *_localStr, XYBlockValueCapture *_self,
int flags=0)
: localNum(_localNum), localObj(_localObj), localStr(_localStr), self(_self)
- 第一行:构造函数名,跟之前分析的一样;
- 第二行和第四行:也是跟之前一样的参数,分别是函数指针、block描述、block标识;
- 第三行:有四个参数,不多不少,刚好是上面编译出来的结构体中多出来的四个属性;
对于int
(数字类型),这里可以清晰的看到是值传递
。对于一个函数的值传递
,形参的变化是无法影响到实参的,所以这个localNum不管怎么变化,都不会影响到block外面的localNum。
对于NSObject *
,我们传递的是指针,所以我们可以通过指针修改对象内部的属性
,但我们能直接修改这个指针吗?很明显是不可以的。如果把NSObject *
作为一个整体,这个指针也是值传递
,对于值传递的变量,我们是不能通过修改形参而修改实参的。所以在block中直接修改局部变量,编译器会抛出异常。 - 第五行:这是c++语法中的一种格式,表示设置属性的初始值,设置完初始值就会
持有这些属性,如果是对象,就会造成引用计数的加一,这个是循环引用的关键
,对于数字类型,持有的是他们的值。
block的实现
block的声明里面介绍了block会持有所有捕获的局部变量,而且是通过copy的方式绑定。那么在使用的时候就比较简单了,第一个参数是结构体本身,第二个参数是block接受的参数列表,要使用block捕获的局部变量,直接从block结构体中取出来即可。
static int __XYBlockValueCapture__testBlockFunc_block_func_0(struct __XYBlockValueCapture__testBlockFunc_block_impl_0 *__cself, int code) {
int localNum = __cself->localNum; // bound by copy
NSObject *localObj = __cself->localObj; // bound by copy
NSString *localStr = __cself->localStr; // bound by copy
XYBlockValueCapture *self = __cself->self; // bound by copy
bound by copy
:通过copy
的方式绑定
block的调用
-
实现testBlock
:调用block的构造方法__XYBlockValueCapture__testBlockFunc_block_impl_0
创建一个block变量,相对于上面的例子,这里就是多了localNum, localObj, localStr, self
四个参数,其他的逻辑完全一致。
XYTestBlock testBlock =
((int (*)(int))
&__XYBlockValueCapture__testBlockFunc_block_impl_0(
(void *)__XYBlockValueCapture__testBlockFunc_block_func_0,
&__XYBlockValueCapture__testBlockFunc_block_desc_0_DATA,
localNum, localObj, localStr, self,
570425344)
);
-
调用testBlock
:最终目的就是调用到函数指针FuncPtr
,最后一行表示这个函数指针接受的参数列表。
(
(int (*)(__block_impl *, int))
((__block_impl *)testBlock)->FuncPtr
)
((__block_impl *)testBlock, 110)
3.2 全局变量的捕获
全局变量可不可以修改呢?其实上面已经给出了答案,答案就在这句话里——对于NSObject *,我们传递的是指针,所以我们可以通过指针修改对象内部的属性
。所有的全局变量都是指针self
的属性,我们都可以通过self
来修改对象内部的属性。
也许你会有这么一个疑问,调用self.globalNum=130
的方式是通过调用了属性的setter方法,在block中调用方法本身就是没什么问题的。那我们尝试直接对成员变量赋值试试:

Xcode告诉我们,self
将会被隐式持有,建议我们显示调用self
,再来看看这行代码在cpp文件中是什么表现形式
(*(int *)((char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum)) = 120;
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setGlobalNum:"), 130);
第二行完全证明了我们上述的猜测,self.globalNum=130
就是调用了属性的setter方法进行的赋值,这行代码也比较简单读懂,就是objc_msgSend
消息发送的流程。对oc消息的查找和转发不熟悉的话,可以看这几篇文章:01--方法本质02--objc_msgSend的使用。
第一行的话,其实需要了解对象的原理,关于对象的成员变量的存储原理,我们可以看下cpp文件中的相关代码:
extern "C" unsigned long int OBJC_IVAR_$_XYBlockValueCapture$_globalNum __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct XYBlockValueCapture, _globalNum);
extern "C" unsigned long int OBJC_IVAR_$_XYBlockValueCapture$_globalObj __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct XYBlockValueCapture, _globalObj);
extern "C" unsigned long int OBJC_IVAR_$_XYBlockValueCapture$_globalStr __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct XYBlockValueCapture, _globalStr);
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[3];
} _OBJC_$_INSTANCE_VARIABLES_XYBlockValueCapture __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
3,
{{(unsigned long int *)&OBJC_IVAR_$_XYBlockValueCapture$_globalNum, "_globalNum", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_XYBlockValueCapture$_globalObj, "_globalObj", "@\"NSObject\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_XYBlockValueCapture$_globalStr, "_globalStr", "@\"NSString\"", 3, 8}}
};
从上面的定义中可以看出OBJC_IVAR_$_XYBlockValueCapture$_globalNum
存储的是成员变量_globalNum
相对于对象首地址的偏移量,所以有了以下的读取步骤:
- 寻找_globalNum指针,
((char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum)
; - 因为它是 int *的指针类型,所以需要强转一次,
(int *)((char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum)
- 拿到_globalNum指针之后,通过指针给变量重新赋值,
(*(int *)((char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum)) = 120;
。我们可以将这行代码改写成以下形式
char * globalNumP0 = (char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum;
int * globalNumP1 = (int *) globalNumP0
*(globalNumP1) = 120;
到这里,我们就完全读完block对全局变量使用的情况。
3.3 self的捕获
根据上面分析的一些逻辑来看,block是将self通过值捕获的方式进行了引用,block内部会持有self。如果说这个block被self持有,那么很明显会造成循环引用。最常用的手段就是通过 weakself
来解决循环引用。
XYBlockValueCapture *self = __cself->self; // bound by copy
3.4 __block的捕获
在很多场景中,我们是需要在block中修改变量的,我们的常规做法是使用__block
来修饰这个变量。为什么这么做就可以了呢?上源码,看原理😄
-
XYBlockValueCapture.m
文件,因为xcrun在转写__block NSString *
变量时发生了错误,所以这里就只用了两个变量,具体原因有兴趣的话可以自己去研究。
xcrun错误
- (void)testBlockFunc2 {
__block int localNum2 = 10;
__block NSObject *localObj2 = [NSObject new];
XYTestBlock testBlock2 = ^(int code) {
localNum2 = 140;
localObj2 = [NSObject new];
NSLog(@"local variable 2 >> num : %d >> obj : %@", localNum2, localObj2);
return 0;
};
NSLog(@"block return : %d", testBlock2(150));
}
-
XYBlockValueCapture.cpp
文件,这一次的文件中比前几次都多出了几种类型。这就是__block
修饰的玄机。还是老规矩,我们按照block的声明
、block的实现
、block的调用
三个步骤来分析。
typedef int(*XYTestBlock)(int code);
// @implementation XYBlockValueCapture
struct __Block_byref_localNum2_0 {
void *__isa;
__Block_byref_localNum2_0 *__forwarding;
int __flags;
int __size;
int localNum2;
};
struct __Block_byref_localObj2_1 {
void *__isa;
__Block_byref_localObj2_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *localObj2;
};
struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 {
struct __block_impl impl;
struct __XYBlockValueCapture__testBlockFunc2_block_desc_0* Desc;
__Block_byref_localNum2_0 *localNum2; // by ref
__Block_byref_localObj2_1 *localObj2; // by ref
__XYBlockValueCapture__testBlockFunc2_block_impl_0(void *fp, struct __XYBlockValueCapture__testBlockFunc2_block_desc_0 *desc, __Block_byref_localNum2_0 *_localNum2, __Block_byref_localObj2_1 *_localObj2, int flags=0) : localNum2(_localNum2->__forwarding), localObj2(_localObj2->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __XYBlockValueCapture__testBlockFunc2_block_func_0(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 *__cself, int code) {
__Block_byref_localNum2_0 *localNum2 = __cself->localNum2; // bound by ref
__Block_byref_localObj2_1 *localObj2 = __cself->localObj2; // bound by ref
(localNum2->__forwarding->localNum2) = 140;
(localObj2->__forwarding->localObj2) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_1ef520_mi_0, (localNum2->__forwarding->localNum2), (localObj2->__forwarding->localObj2));
return 0;
}
static void __XYBlockValueCapture__testBlockFunc2_block_copy_0(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*dst, struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*src) {_Block_object_assign((void*)&dst->localNum2, (void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->localObj2, (void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __XYBlockValueCapture__testBlockFunc2_block_dispose_0(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*src) {_Block_object_dispose((void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __XYBlockValueCapture__testBlockFunc2_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*, struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*);
void (*dispose)(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*);
} __XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA = { 0, sizeof(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0), __XYBlockValueCapture__testBlockFunc2_block_copy_0, __XYBlockValueCapture__testBlockFunc2_block_dispose_0};
static void _I_XYBlockValueCapture_testBlockFunc2(XYBlockValueCapture * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_localNum2_0 localNum2 = {(void*)0,(__Block_byref_localNum2_0 *)&localNum2, 0, sizeof(__Block_byref_localNum2_0), 10};
__attribute__((__blocks__(byref))) __Block_byref_localObj2_1 localObj2 = {(void*)0,(__Block_byref_localObj2_1 *)&localObj2, 33554432, sizeof(__Block_byref_localObj2_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
XYTestBlock testBlock2 = ((int (*)(int))&__XYBlockValueCapture__testBlockFunc2_block_impl_0((void *)__XYBlockValueCapture__testBlockFunc2_block_func_0, &__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA, (__Block_byref_localNum2_0 *)&localNum2, (__Block_byref_localObj2_1 *)&localObj2, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_1ef520_mi_1, ((int (*)(__block_impl *, int))((__block_impl *)testBlock2)->FuncPtr)((__block_impl *)testBlock2, 150));
}
// @end
block的声明
相同部分就不再累述,直接看新增、变化的部分。
我们在外面定义的是一个 int
和一个 NSObject *
的两个变量
__block int localNum2 = 10;
__block NSObject *localObj2 = [NSObject new];
结果在源码中被转成了__Block_byref_localNum2_0 *
、__Block_byref_localObj2_1 *
这样的两个类型。
__Block_byref_localNum2_0 *localNum2; // by ref
__Block_byref_localObj2_1 *localObj2; // by ref
下面再来看看这两个类型的源码。
先看相同之处
- 它们都被包装成了一个新的对象的属性;
- 它们在这个新的对象中的属性名,和外面定义的变量名相同;
- 新的对象中都包含一个只想自己的指针
__forwarding
。(TIP:为什么不直接用self,还要额外搞一个指向?); - 都有
__flags
和__size
属性,这两个属性不在分析范畴内;
struct __Block_byref_localNum2_0 {
void *__isa;
__Block_byref_localNum2_0 *__forwarding;
int __flags;
int __size;
int localNum2;
};
struct __Block_byref_localObj2_1 {
void *__isa;
__Block_byref_localObj2_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *localObj2;
};
再看不同之处:包含NSObject *
的对象多了两个方法
-
__Block_byref_id_object_copy
,对象拷贝 -
__Block_byref_id_object_dispose
,对象释放
从源码中可以看出这两个方法最终会调用到系统的两个方法。关于这两个方法,会在后面两节中讲解,本节重点是 __block
_Block_object_assign
_Block_object_dispose
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
block的实现
这里使用的对象的__forwarding
中的值,forward是先前的,也就是我们捕获时候的值,系统为什么要区分不同时机的值呢?那它本身的值留着又有什么用呢?这些疑问都留到后面来解答。回到最初的问题,为什么使用__block
就可以修改变量的值了。我想答案不明而喻。
block对使用__block
修饰的变量做了一层包装,构建出一个新的对象,而block中在使用这个变量的时候,其实是作为新的对象的属性在使用,回到我们上面关于全局变量的值捕获
的结论,block可以通过指针修改对象内部的属性,这就是__block
的玄机。
static int __XYBlockValueCapture__testBlockFunc2_block_func_0(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 *__cself, int code) {
__Block_byref_localNum2_0 *localNum2 = __cself->localNum2; // bound by ref
__Block_byref_localObj2_1 *localObj2 = __cself->localObj2; // bound by ref
(localNum2->__forwarding->localNum2) = 140;
(localObj2->__forwarding->localObj2) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_1ef520_mi_0, (localNum2->__forwarding->localNum2), (localObj2->__forwarding->localObj2));
return 0;
}
block的调用
__block
的玄机是搞懂了,但是在阅读调用处的代码,会发现系统又给我们很多新的东西
static void _I_XYBlockValueCapture_testBlockFunc2(XYBlockValueCapture * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_localNum2_0 localNum2 = {(void*)0,(__Block_byref_localNum2_0 *)&localNum2, 0, sizeof(__Block_byref_localNum2_0), 10};
__attribute__((__blocks__(byref))) __Block_byref_localObj2_1 localObj2 = {(void*)0,(__Block_byref_localObj2_1 *)&localObj2, 33554432, sizeof(__Block_byref_localObj2_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
XYTestBlock testBlock2 = ((int (*)(int))&__XYBlockValueCapture__testBlockFunc2_block_impl_0((void *)__XYBlockValueCapture__testBlockFunc2_block_func_0, &__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA, (__Block_byref_localNum2_0 *)&localNum2, (__Block_byref_localObj2_1 *)&localObj2, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_1ef520_mi_1, ((int (*)(__block_impl *, int))((__block_impl *)testBlock2)->FuncPtr)((__block_impl *)testBlock2, 150));
}
block狠起来连自己都不放过,它除了拷贝了变量,它还把自己也拷贝了一份,上源码😁
static struct __XYBlockValueCapture__testBlockFunc2_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*, struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*);
void (*dispose)(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*);
} __XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA = {
0,
sizeof(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0),
__XYBlockValueCapture__testBlockFunc2_block_copy_0,
__XYBlockValueCapture__testBlockFunc2_block_dispose_0
};
static void __XYBlockValueCapture__testBlockFunc2_block_copy_0(
struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 *dst,
struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 *src) {
_Block_object_assign((void*)&dst->localNum2, (void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->localObj2, (void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __XYBlockValueCapture__testBlockFunc2_block_dispose_0(
struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*src) {
_Block_object_dispose((void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);
}
3.5 block值捕获总结
- block中不能修改变量本身;
- block中可以通过指针修改对象内部属性;
四、block的拷贝流程
在上面的分析中发现了block内部其实还有新的流程,而对于这个流程的分析可以通过汇编和源码的方式。关于汇编的探索,这里就省了,会在逆向的文章中重点介绍汇编相关的知识,这里就直接来分析源码。libclosure-79下载地址,下载最新的源码即可

-
Block_private.h
:block头文件 -
runtime.cpp
:block实现文件
block
4.1 Block_private.h
block的本质
block的本质
从头文件的定义中可以看出block的本质是一个结构体,这里对block结构体的定义的类型,与我们上面编译出来的cpp文件中的结构体是完全一致的。这里的四个基本属性上面都有详细的说明。
// XY注释:block 结构体
struct Block_layout {
// 指向表明block的类型
void * __ptrauth_objc_isa_pointer isa;
// 标识符,类似isa的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count
// 保留字段,用于存储block内部变量信息
int32_t reserved;
// 函数指针,指向具体的block函数的调用地址
BlockInvokeFunction invoke;
// block的描述信息
struct Block_descriptor_1 *descriptor;
// imported variables
};
block->flags 的本质
在上面的分析中,可以知道每个block都有一个flags属性。对于标识类属性,苹果惯用isa位域这一套,按位标识不同的含义,下面对每一位标识的含义都有清晰的注释,可以查看。
// Values for Block_layout->flags to describe block objects
// XY注释:flags 标识
enum {
// 释放标记,用于与 BLOCK_BYREF_NEEDS_FREE 按位与,一同传入flags,表示该block可以释放了
BLOCK_DEALLOCATING = (0x0001), // runtime
// 存储引用计数的值
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
// 低16位是否有效的标识,程序根据它来决定是否增加或者减少引用计数位的值
BLOCK_NEEDS_FREE = (1 << 24), // runtime
// 标识是否有拷贝和释放函数,决定 Block_descriptor_2
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
// 表示是否有block C++析构函数
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
// 标识是否有垃圾回收,OSX
BLOCK_IS_GC = (1 << 27), // runtime
// 标识是否是全局block
BLOCK_IS_GLOBAL = (1 << 28), // compiler
// 标识是否有签名,用于runtime时动态调用,与BLOCK_HAS_SIGNATURE相对
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
// 标识是否有签名
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
// 表示是否使用扩展,决定Block_descriptor_3
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
block->Block_descriptor 本质
上面对cpp文件的分析中,已经出现了两种类型的block,而不同类型的block的描述信息也是不一样的。例如:对__block
修饰的变量的捕获,就会构建一个新的对象,由这个对象来修改这个变量。同时,还会添加copy、dispose函数,用来对block的拷贝和释放。这三种description如何取值,后面的流程中会给出。
下面的三种 description 对应的是block的三种值捕获的描述
- Block_descriptor_1:普通局部变量的捕获,直接拷贝指针(值)即可
- BLOCK_DESCRIPTOR_2:__block变量的捕获,构建新的对象,新增copy和dispose函数
- BLOCK_DESCRIPTOR_3:从字段名可以看出,这个结构体是对block的一个扩展描述。每个block都是默认的4个属性,但当block捕获变量之后,就会将这些捕获的变量包装到内部,构成一个新的结构体,这个时候block的内存布局信息就发生了变化,而这个字段就是用来描述block新的布局内存的。
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
// 保留信息
uintptr_t reserved;
// block大小
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
// 拷贝block函数
BlockCopyFunction copy;
// 释放block函数
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
// 签名 依赖 BLOCK_HAS_SIGNATURE 字段
const char *signature;
// 布局 依赖 BLOCK_HAS_EXTENDED_LAYOUT 字段
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
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;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
Block_byref->flags 本质
// Values for Block_byref->flags to describe __block variables
// XY注释:flags 标识
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
// block引用位域面罩
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
// block引用扩展
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
// block引用非对象类型
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
// block引用对象类型
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
// block引用weak类型
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
// block引用类型未定义,将来需要扩展字段的开始
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
// block引用是否垃圾回收
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
// block引用是否有copy和dispose函数
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
// block引用释放标识
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
到这里,基本上对block中会出现的所有类型都已经分析完了。这里只是静态的过了一遍定义,并不知道如何使用,block是如何拷贝的,接下来需要对cpp文件的分析来看block的拷贝过程。
4.2 汇编调试
-
上代码
测试拷贝
-
开汇编
汇编调试
-
现原形
block本质
通过汇编的方式,我们找到了跟block有关的信息,然后开启断点调试。需要链接真机调试,才可以看到调试信息,有些信息在x86架构里面是没有的。

从上面的对象信息中可以看到block结构体中的信息都在这里:
signature: "v8@?0"
invoke : 0x102391d30
copy : 0x102391d90
dispose : 0x102391dc8
我们知道第二位是flags标识,可以打印出来看下效果

查看是否有:Block_descriptor_2
,p/t flags & 1<<25
查看是否有:Block_descriptor_3
,p/t flags & 1<<30
从下面的结果中可以看出,都是存在的

查看签名信息:po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]

//无返回值
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
//encoding = (@),类型是 @?
type encoding (@) '@?'
//@是isObject ,?是isBlock,代表 isBlockObject
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
//所在偏移位置是8字节
memory {offset = 0, size = 8}
当我们在其他地方输出block时,发现这个时候的block已经变成了堆block:__NSMallocBlock__

4.3 block的拷贝流程
上面的那么多内容都是值望梅解渴,并没有真正的深入到block的内部,上源码😁
_Block_object_assign
这个方法是block以及block引用对象拷贝的入口方法。
// XY注释:block以及block引用拷贝的入口点
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(object);
*dest = object;
break;
// block变量的值捕获
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
// __block包装的变量的值捕获
// __weak在ARC下,由系统直接处理,后面提到的__weak都是针对MRC下的场景
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;
default:
break;
}
}
我们可以回到前面的源码中出现的几种情况:
- 对于整数类型:block是直接拷贝了整数的值;
- 对于对象类型:调用了
_Block_object_assign
进行拷贝,而且注释中显式标明了类型3/*BLOCK_FIELD_IS_OBJECT*/
static void __XYBlockValueCapture__testBlockFunc_block_copy_0(
struct __XYBlockValueCapture__testBlockFunc_block_impl_0*dst,
struct __XYBlockValueCapture__testBlockFunc_block_impl_0*src) {
_Block_object_assign((void*)&dst->localObj, (void*)src->localObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->localStr, (void*)src->localStr, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
- 对于block类型:标明了类型
7/*BLOCK_FIELD_IS_BLOCK*/
static void __XYBlockValueCapture__testBlockFunc3_block_copy_1(
struct __XYBlockValueCapture__testBlockFunc3_block_impl_1*dst,
struct __XYBlockValueCapture__testBlockFunc3_block_impl_1*src) {
_Block_object_assign((void*)&dst->testBlock1, (void*)src->testBlock1, 7/*BLOCK_FIELD_IS_BLOCK*/);
}
- 对于__block修饰的类型:标明了类型
8/*BLOCK_FIELD_IS_BYREF*/
static void __XYBlockValueCapture__testBlockFunc2_block_copy_0(
struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*dst,
struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*src) {
_Block_object_assign((void*)&dst->localNum2, (void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->localObj2, (void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);
}
再看看下这几种类型的定义说明
// Values for _Block_object_assign() and _Block_object_dispose() parameters
// XY注释:block捕获的值的类型
enum {
// see function implementation for a more complete description of these fields and combinations
// block捕获的值是对象
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
// block捕获的值是block
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
// block捕获的值是__block修饰的变量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
// block捕获的值是weak对象
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.
};
_Block_copy 源码
- 如果是全局block,不拷贝,直接返回;
- 只有栈block,才会被拷贝到堆block里面;
- 新的堆block,引用计数默认为1。
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
- 设置block类型为堆block。
result->isa = _NSConcreteMallocBlock;
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
// XY注释:拷贝block
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;
// 如果是正准备释放的block,不进行拷贝
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果是全局block,则直接返回
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
// 对栈block进行拷贝
size_t size = Block_size(aBlock);
// 在堆上开辟一个新的内存
struct Block_layout *result = (struct Block_layout *)malloc(size);
if (!result) return NULL;
// 按位拷贝
memmove(result, aBlock, size); // bitcopy first
// reset refcount
// 重设block的引用计数
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 引用计数设置为1
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_byref_copy 源码
对引用对象的拷贝,只有变量被__block
修饰的时候,才会走到这个流程里面。当引用对象被拷贝时,它的引用计数会被设置为2
- 一个是copy给自己,以供外部调用;
- 一个是返回给栈block。(栈block原先的指向就会被释放)
// XY注释:拷贝引用对象,避免在解除了forwarding指针的引用之后,仍然会出现访问他的问题
static struct Block_byref *_Block_byref_copy(const void *arg) {
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
// 将block引用对象的引用计数设置为2,一个是调用者持有,一个是栈指针持有
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// 将copy给自己,以供调用者使用
copy->forwarding = copy; // patch heap copy to point to itself
// 将copy给栈指针,栈指针原先的指向会被释放
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;
}
拷贝何时发生
__attribute__((__blocks__(byref))) __Block_byref_localObj2_1 localObj2 = {
(void*)0,(__Block_byref_localObj2_1 *)&localObj2, 33554432, sizeof(__Block_byref_localObj2_1),
__Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))
};
XYTestBlock testBlock2 =
((int (*)(int))&__XYBlockValueCapture__testBlockFunc2_block_impl_0(
(void *)__XYBlockValueCapture__testBlockFunc2_block_func_0, &__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA,
(__Block_byref_localNum2_0 *)&localNum2, (__Block_byref_localObj2_1 *)&localObj2, 570425344)
);
回到上面对__block
修饰的源码中,我们先不考虑拷贝是怎么发生的,我们知道了被捕获的对象会被包装成一个新的对象,新的对象又会作为这个block的属性存在,所以我们按照这个思路来分析。
-
NSObject对象拷贝进新的引用对象
__Block_byref_localObj2_1,函数:__Block_byref_id_object_copy_131 -
localObj2引用对象拷贝进block
__XYBlockValueCapture__testBlockFunc2_block_impl_0,函数:__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA -
栈block拷贝进堆
,testBlock2 = __XYBlockValueCapture__testBlockFunc2_block_impl_0,函数:__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA
当拷贝完成之后,我们来分析下各自的持有状态。
-
堆block->block_byref->object
:block_byref引用计数+1,object引用计数+1; -
栈block->block_byref->object
:block_byref引用计数+1,而object还是被block_byref所持有,所以它的引用计数只+1;
当这行代码执行完成之后,栈block(=右边的block)没有被持有,将会立即释放,所以最终的持有关系链,只剩下堆block->block_byref->object
,也就是我们测试代码中的testBlock2
4.4 block的释放流程
那剩下的堆block何时释放呢?首先,block也是一个对象,既然是对象,那么也会遵从ARC协议,当它的引用计数为0的时候释放。
_Block_object_dispose 源码
// XY注释:block释放的流程
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_byref对象
,block的本身的释放时机是有ARC管理,也就是引用计数为0的时候释放。
释放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_byref的代码:_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类型或者是__block类型,需要释放内部拷贝的对象,如果是object类型,将引用计数-1即可;
- 释放堆block;
五、FAQ
5.1 为什么block内部能不修改外部变量
block的函数性
前面提到了很多次,block曾一度被认为是函数指针。既然是函数指针,那block内部可以被理解为函数的作用域。在源码中可以看出来,外部变量是通过函数形参的方式传递进block内部的。众所周知,形参的改变是不会影响到实参的变化。从语法上来看,我们可以在block内部来修改这个值,但实际上我们并没有真正的修改这个值,所以为了避免理解上的歧义,Apple不允许在block内部修改外部变量。
5.2 怎么理解栈block
- 函数是要在入栈被调用的,而在函数里面的变量也在栈上;
- =右边的block,是在函数里实现的,所以右边的block是栈block;
- 栈空间是宝贵的,在函数出栈之后,这里分配的空间都将被回收;
- 所以一般情况下,需要使用block的时候,都会对block进行一次拷贝,将其拷贝到堆上,并将=左边的变量指向堆上block;
- 在ARC机制下,在函数结尾处,会对这些临时变量发送release消息。
- 当收到release消息的=左边的对象的引用计数变为0,开始释放堆block。
=右边,栈block在函数出栈之后被释放;
=左边,引用计数变为0之后被释放;
5.3 为什么设计block
block的值捕获
说了很多次block的函数性,那有了函数指针,为什么还需要block呢?
函数是一个代码块,有参数列表,内部可以定义局部变量。函数在调用的时候会入栈,调用结束后会出栈,所有的局部变量都将会被释放,而形参只能是从外面传递进来的,实参的管理完全依赖外部,在多线程的场景下,极有可能出现调用函数时实参并不是自己所期待的值、或者已经被释放。
block会捕获当前的环境变量,构建新的block类型将捕获的变量进行包装,然后将block对象拷贝到堆上。以确保block在调用的时候,环境变量仍是当初捕获值,不会在因为环境变量在外部的修改而影响block内部的实现逻辑,以确保延时调用时的环境没有变化。
5.4 为什么设计__forwarding
forwarding
单词表示的是转发
的含义。结合上面对block分析的理解,当发生copy的时候,栈block会被拷贝到堆上,这是会在堆上拷贝一个新的block对象,而block对捕获的值(不考虑__block)也是copy,这时,被捕获的值实际上存在两个副本。假如没有__forwarding
这个指针,当栈block执行的时候,修改的栈上捕获的值,当出栈时,所有修改的信息都将会被丢弃,而堆block上的值没有任何变化,这种情况,会造成一些难以预期的错误,所以设计出__forwarding
指针。让栈block的__forwarding
指针也指向堆block,这样可以保证这次修改和以后将要修改的值都是同一份。
网友评论