结论
Block本质上也是一个OC对象
Block是封装了函数调用以及函数调用环境的OC对象
1、Block的本质认识
1.1、Block是什么
先写一个简单的block代码(macOS程序)如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 18;
void(^block)(int, int) = ^(int a, int b) {
NSLog(@"%i", age);
NSLog(@"%i", a);
NSLog(@"%i", b);
};
block(10, 20);
}
return 0;
利用clang编译以上代码,命令如下:
`xcrun - sdk iphoneos clang -arch arm64 -rewrite-objc main.m`
查看编译出的main.cpp文件
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 18;
void(*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
struct __main_block_impl_0* blockStruct = (__bridge struct __main_block_impl_0*)block;
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
}
return 0;
}
通过C++代码分析,block被定义为了__main_block_impl_0 类型的结构体:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
其中impl的定义为如下结构体:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
如果将impl的定义直接放入到__main_block_impl_0结构体中,我们可以发现其第一个变量即是isa指针(OC对象isa也是第一变量),因此block在本质上讲在底层实现上来看其就是一个OC对象。
1.2、Block布局结构

查看main.cpp中block的定义以及实现代码:
void(*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
以及__main_block_func_0的内容:
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w5_2p585ycs4vsfdrh02hdfjwtw0000gn_T_main_a96687_mi_0, age);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w5_2p585ycs4vsfdrh02hdfjwtw0000gn_T_main_a96687_mi_1, a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w5_2p585ycs4vsfdrh02hdfjwtw0000gn_T_main_a96687_mi_2, b);
}
以上可以分析出:
__main_block_func_0函数作为参数传递给了__main_block_impl_0函数, 既fp函数指针,而fp函数指针又赋值给了__main_block_impl_0中的impl的FuncPtr函数指针
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
而block的调用就是找到block对象的FuncPtr,然后调用
分析FuncPtr可知:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
NSString *str;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, NSString *_str, int flags=0) : age(_age), str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
因impl为__main_block_impl_0首元素,通过block->FuncPtr可直接找到__main_block_func_0函数
1.3 如何验证block为__main_block_impl_0 结构体?
我们按照上述分析,定义出三个结构体,并在main.m中增加一行对block的引用
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
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 age;
NSString *str;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 18;
void(^block)(int, int) = ^(int a, int b) {
NSLog(@"%i", age);
NSLog(@"%i", a);
NSLog(@"%i", b);
};
struct __main_block_impl_0* blockStruct = (__bridge struct __main_block_impl_0*)block;
block(10, 20);
}
return 0;
}turn 0;
}
分别在block中和blockStruct加断点

可以看到age变量会被block捕获
打开汇编断点

继续执行断点

对比发现blockStruct中的FuncPtr和block断点到的函数起始地址的值是一致的。
2、Block对变量的捕获
变量类型 | 是否捕获到block内部 | 变量访问方式 |
---|---|---|
局部变量(auto) | ✔️ | 值传递 |
局部变量(static) | ✔️ | 指针传递 |
全局变量 | ❌ | 直接访问 |
2.1、局部变量--auto变量
有如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 18;
void(^block)(void) = ^() {
NSLog(@"%i", age);
};
age = 28;
block();
}
return 0;
}
查看block对应代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
通过
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age)
可以分析出,__main_block_impl_0结构体内部声明了一个age 来接收_age的值,即block对于age的捕获是值捕获。所以block外部age的修改,并不影响block内部age的值,所以执行block后的age值仍然是18。
2.2、局部变量--static变量
void(^block)(void);
void testBlock(){
auto int age = 18;
static int weight = 100;
block = ^{
NSLog(@"age=%i weight=%i", age, weight);
};
age = 20;
weight = 120;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock();
block();
}
return 0;
}
查看block对应代码:
struct __testBlock_block_impl_0 {
struct __block_impl impl;
struct __testBlock_block_desc_0* Desc;
int age;
int *weight;
__testBlock_block_impl_0(void *fp, struct __testBlock_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到block对于static类型变量的捕获方式为地址捕获
思考:auto 与static 声明的变量,为什么捕获方式不同?
- auto变量生命周期小于block生命周期,所以block需要捕获变量值
- static变量生命周期大于等于block生命周期,所以block只需要捕获变量指针(地址)
2.3、全局变量
全局变量,不需要捕获,直接访问。
如下测试代码:
int height = 88;
void(^block)(void);
void testBlock(){
block = ^{
NSLog(@"%i", height);
};
height = 180;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock();
block();
}
return 0;
}
.cpp查看
static void __testBlock_block_func_0(struct __testBlock_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w5_2p585ycs4vsfdrh02hdfjwtw0000gn_T_main_8e351d_mi_0, height);
}
可以看到对于全局变量,block会直接访问该变量。
2.4 self的捕获
创建一个类,加上如下方法:
#import "TestObject.h"
@implementation TestObject
void(^block)(void);
- (void)testMethod
{
block = ^{
NSLog(@"%@", self);
};
}
@end
查看.cpp文件
static void _I_TestObject_testMethod(TestObject * self, SEL _cmd) {
block = ((void (*)())&__TestObject__testMethod_block_impl_0((void *)__TestObject__testMethod_block_func_0, &__TestObject__testMethod_block_desc_0_DATA, self, 570425344));
}
static void __TestObject__testMethod_block_func_0(struct __TestObject__testMethod_block_impl_0 *__cself) {
TestObject *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w5_2p585ycs4vsfdrh02hdfjwtw0000gn_T_TestObject_8441b2_mi_0, self);
}
可以看到调用testMethod方法的时候,self作为参数(局部变量)传递给方法使用,block会捕获self。
2.5 对象成员变量的捕获
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
@property (nonatomic, assign) NSInteger age;
@end
#import "TestObject.h"
@implementation TestObject
void(^block)(void);
- (void)testMethod
{
block = ^{
NSLog(@"%d", _age);
};
}
@end
查看.cpp
static void _I_TestObject_testMethod(TestObject * self, SEL _cmd) {
block = ((void (*)())&__TestObject__testMethod_block_impl_0((void *)__TestObject__testMethod_block_func_0, &__TestObject__testMethod_block_desc_0_DATA, self, 570425344));
}
static void __TestObject__testMethod_block_func_0(struct __TestObject__testMethod_block_impl_0 *__cself) {
TestObject *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w5_2p585ycs4vsfdrh02hdfjwtw0000gn_T_TestObject_feb7ec_mi_0, (*(NSInteger *)((char *)self + OBJC_IVAR_$_TestObject$_age)));
}
可以看到,对于成员变量的捕获是捕获了self,然后根据(*(NSInteger *)((char *)self + OBJC_IVAR_$_TestObject$_age))
找到age的值。
对于其他情况下block对于变量的捕获原则就是,如果变量是局部变量,则会捕获,全局变量不捕获!!!
3、Block的类型
3.1、block也是一种OC对象

通过isa可以直接查看,如下代码也可以验证。
void(^block)(void);
void testBlock(){
block = ^{
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock();
block();
}
return 0;
}
查看打印结果:
2019-08-25 23:07:44.800168+0800 studyBlock[8665:371790] __NSGlobalBlock__
2019-08-25 23:07:44.800380+0800 studyBlock[8665:371790] __NSGlobalBlock
2019-08-25 23:07:44.800399+0800 studyBlock[8665:371790] NSBlock
2019-08-25 23:07:44.800414+0800 studyBlock[8665:371790] NSObject
由此可以看出,block也是一种OC对象
3.2、三种类型的block
NSGlobalBlocck NSStackBlock NSMallocBlock
block有如上三种类型
如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block1)(void) = ^{
NSLog(@"block1");
};
int age = 18;
void (^block2)(void) = ^{
NSLog(@"block2-%d", age);
};
NSLog(@"%@ \n %@ \n %@ \n", [block1 class], [block2 class], [^{
NSLog(@"block3 %d", age);
} class]);
}
return 0;
}
执行打印结果如下:
NSGlobalBlock
NSMallocBlock
NSStackBlock
3种Block的内存位置如下图:

三种Block的判断:

3.3、三种类型的block对于对象类型的auto变量的访问
栈上的block对于对象类型的auto变量,不管对于auto变量是strong修饰还是weak修饰,都不会强引用该变量,因为栈上的block生命周期短暂,随时都可能销毁。
而栈上的block在被copy到堆上的时候,会根据auto的修饰符是strong还是weak,产生不同的效果。 copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)
copy和dispose的调用时机如下:

__weak问题解决
在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
4、Block对变量的修改
因block对于局部变量的捕获是值捕获,所以无法修改变量(block捕获的变量和局部变量在不同的函数栈) 修改方式:
把局部变量用static修饰
把局部变量升级为全局变量
添加__block修饰符
__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 18;
^{
NSLog(@"%d", age);
}();
}
return 0;
}
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m编译后查看block:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
age被包装成如下对象:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
被__block修饰的变量包装成的对象结构如下:

5、Block的内存管理
5.1、block对外部变量的引用
如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 18;
NSObject* obj = [[NSObject alloc] init];
__weak NSObject* weakObj = obj;
^{
NSLog(@"%d", age);
NSLog(@"%@", obj);
NSLog(@"%@", weakObj);
}();
}
return 0;
}
编译上面代码:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
查看:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__strong obj;
NSObject *__weak weakObj;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, NSObject *__weak _weakObj, __Block_byref_age_0 *_age, int flags=0) : obj(_obj), weakObj(_weakObj), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
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};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
当block在栈上时,并不会对__block变量产生强引用
当block被copy到堆时 会调用block内部的copy函数 copy函数内部会调用_Block_object_assign函数 _Block_object_assign函数会对__block变量形成强引用(retain)


static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
当block从堆中移除时 会调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的__block变量(release)


5.2、__block的__forwarding指针


forwarding指针保证了访问栈和堆上的block时捕获的变量的一致性。
5.2、被__Block修饰的对象类型
如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 18;
__block TestObject* obj = [[TestObject alloc] init];
//__weak TestObject* weakObj = obj;
^{
NSLog(@"%@", obj);
}();
}
return 0;
}
编译查看:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_1 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_1 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
TestObject *obj;
};
如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObject* obj = [[TestObject alloc] init];
__block __weak TestObject* weakObj = obj;
^{
NSLog(@"%@", weakObj);
}();
}
return 0;
}
编译查看:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakObj_0 *weakObj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakObj_0 *_weakObj, int flags=0) : weakObj(_weakObj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_weakObj_0 {
void *__isa;
__Block_byref_weakObj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
TestObject *__weak weakObj;
};
可以看到,__main_block_impl_0中__Block_byref_weakObj_0指针指向__Block_byref_weakObj_0结构体,而__Block_byref_obj_1对于obj对象的引用是strong还是weak取决于block捕获的变量的修饰符(weak 或者 strong) 内存图如下图所示:

6、Block的循环引用
6.1、循环引用的原因

当一个对象持有block的时候,block如果同时持有该对象,则会引起循环引用问题,导致对象本身和block均不能释放,从而造成内存泄漏问题。
6.1、循环引用的解决
通过__weak、__unsafe_unretained 来解决

两者均不会对对象强引用
而两者的区别在于,__weak 会在对象销毁的时候自动将引用(指针)置为nil,而__unsafe_unretained会继续保留引用的对象的地址值,从未留下野指针隐患。
通过__block来解决, 必须调用blcok
__block id weakSelf = self;
self.blcok = ^{
NSLog(@"%@", weakSelf);
weakSelf = nil;
}
self.block();

如上代码,weakSelf 本身会持有对象,block也会持有对象,对象持有block,三者关系如左图所示,存在三角形循环引用问题,当我们执行block之后,weakSelf被手动置为nil,变为右图所示,解除了循环引用。
在ARC下使用__weak是解决循环引用的最佳姿势。
6.3、防止weakself引发的崩溃或者其他问题
场景一:
当你在某个界面请求网络数据的时候,用户不愿意等待点击了返回按钮,此时在Block当中用如下的方式使用weakSelf的话,有可能会奔溃(因为在并发编程的情况下,虽然在if判断的时候weakself不为空,但是不保证if语句里面的weakself不为空),所以为了安全起见要加上strongSelf
if (weakSelf != nil) {
// last remaining strong reference released by another thread.
// weakSelf is now set to nil.
[myArray addObject:weakSelf];
}
场景二:
在block内部需要执行self相关的任务,而此时weakSelf可能已经释放而无法完成。
分析如下代码:
__weak TestBlock* weakSelf = self;
self.block = ^(){
__strong TestBlock* strongSelf = weakSelf;
NSLog(@"%ld", (long)strongSelf.age);
};
编译查看:
static void __TestBlock__test_block_func_0(struct __TestBlock__test_block_impl_0 *__cself) {
TestBlock *__weak weakSelf = __cself->weakSelf; // bound by copy
__attribute__((objc_ownership(strong))) TestBlock* strongSelf = weakSelf;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w5_2p585ycs4vsfdrh02hdfjwtw0000gn_T_TestBlock_c9c9c2_mi_0, (long)((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)strongSelf, sel_registerName("age")));
}
strongself其实是利用了循环引用的特性保证了在block执行的时候self对象不会被析构,strongSelf是局部变量执行完成后就会释放。