- block的实质是什么?
- 一共有几种block?
- 都是什么情况下生成的?
block的实质是什么?
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block是封装函数及其上下文的OC对象
如何查看block源码:
- 打开终端,在main.m所在目录下键入clang -rewrite-objc main.m即可在当前目录下生成一个main.cpp文件;
- 当引用了OC中的Foundation或者UIKit框架时,通过 clang -rewrite-objc 指定文件名 命令将指定文件转换成C++代码会报错;
- 可通过 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 指定文件名
先写一段简单的block代码:
int main(int argc, char * argv[]) {
@autoreleasepool {
int num = 10;
void(^block)(int ,int) = ^(int a, int b){
NSLog(@"a = %d,b = %d",a,b);
NSLog(@"num = %d",num);
};
block(1,2);
}
return 0;
}
转化为c++源码:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int num = 10;
void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
}
return 0;
}
对比两段代码,发现定义block的源码:
void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
- 调用
__main_block_impl_0函数,并且将函数地址赋值给block。 - 传了三个参数
(void *)__main_block_func_0、&__main_block_desc_0_DATA、num。
__main_block_impl_0结构体:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
-
__main_block_impl_0结构体内有一个同名构造函数__main_block_impl_0,构造函数中带有四个参数。 - 参数1
*fp:对应定义时传过来的(void *)__main_block_func_0函数地址,由__block_impl impl的属性FuncPtr接受。
注意⚠️:该参数记录的是block内代码块的地址。 - 参数2
desc:对应定义时传过来的&__main_block_desc_0_DATA地址,由__main_block_desc_0* Desc接受。
注意⚠️:该参数记录着block对象占用内存的大小。 - 参数3
_num: 对应定义时传过来的num。 - 参数4
flags:默认值0.
定义时将__main_block_impl_0结构体的地址赋值给了block
参数1:(void *)__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int num = __cself->num; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_1c8b6c_mi_0,a,b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_1c8b6c_mi_1,num);
}
- 该函数中首先取出了
num的值。然后就是两个NSLog,就是我们block代码块中的打印。所以我们断定,block代码块中写下的代码被封装成了__main_block_func_0函数。函数地址由__main_block_impl_0结构体中的__block_impl的属性FuncPtr保存。
参数2:&__main_block_desc_0_DATA
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)};
- 参数1
reserved:值默认为0。 - 参数2
Block_size:默认值为sizeof(struct __main_block_impl_0),也就是__main_block_impl_0结构体占用空间的大小。
参数3:_num
我们定义的局部变量。因为在block块内用到了变量num,所以block在声明的时候会将num作为参数传入,捕获参数num。
如果在block块内没有使用到num,就不会作为参数传入。
注意⚠️:这里就是为什么在定义block之后修改局部变量的值,再调用
block,修改的值无法生效的原因。
定义block时已经将局部变量的值传入__main_block_impl_0结构体中,调用block时直接从__main_block_impl_0结构体中将值取出来。
__block_impl结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
-
isa指针:存储着&_NSConcreteStackBlock地址(理解为类对象地址)。block就是_NSConcreteStackBlock类型的。 -
FuncPtr 函数地址:存储着__main_block_func_0函数的地址。也就是block内代码块的地址。
该结构体内含有isa指针,因此可以证明block本质上就是一个OC对象。
调用block(1,2);的源码:
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
- 直接将
block转化为__block_impl类型,取出__main_block_func_0函数的地址FuncPtr(也就是block内代码块的地址)。 - 将
block本身 和 值1、2传过去。
注意⚠️:block是__main_block_impl_0类型的结构体,怎么可以直接强转为__block_impl类型?
因为
__block_impl是__main_block_impl_0结构体的第一个成员,相当于将__block_impl结构体的成员直接拿出来放在__main_block_impl_0中,那么也就说明__block_impl的内存地址就是__main_block_impl_0结构体的内存地址开头。所以可以转化成功。
block捕获变量
我们修改下代码:
int globalNum = 30;
int main(int argc, char * argv[]) {
@autoreleasepool {
int num = 10;
static int staticNum = 20;
void(^block)(int ,int) = ^(int a, int b){
NSLog(@"a = %d,b = %d",a,b);
NSLog(@"num = %d, count = %d, globalCount = %d",num, staticNum, globalNum);
};
num = 5;
staticNum = 15;
block(1,2);
}
return 0;
}
再看下源码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num;
int *staticNum;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int *_staticNum, int flags=0) : num(_num), staticNum(_staticNum) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int num = __cself->num; // bound by copy
int *staticNum = __cself->staticNum; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_c17f7e_mi_0,a,b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_c17f7e_mi_1,num, (*staticNum), globalNum);
}
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)};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int num = 10;
static int staticNum = 20;
void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num, &staticNum));
num = 5;
staticNum = 15;
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
}
return 0;
}
可以看出,num是值传递,staticNum是指针传递& staticNum, globalNum没有传递,而是直接访问。
1、局部变量-自动变量(auto变量)
局部变量前面自动添加auto关键字,auto只存在于局部变量中,离开作用域就销毁。
上述代码中已经验证,自动变量会捕获到block内部,block内部会专门新增加一个参数来存储变量的值。访问方式为 值传递。
2、局部变量-静态变量(static变量)
static修饰的变量同样会被block捕获,访问方式为 指针传递。
局部变量可能会销毁,调用block时如果该变量被销毁了,就不能访问该变量的地址,所以只能传递值。静态变量不会被销毁,所以可以传地址,传地址不回增加内存的消耗。
所以,在block调用之前修改地址中保存的值,block中的地址是不会变的。所以值会随之改变。
3、全局变量
不会被block捕获,不用传递,直接访问。
block内使用self
OC代码:
@implementation CQTest
- (void)testDemo1 {
void(^block)(void) = ^{
NSLog(@"%@",self);
};
block();
}
+ (void)testDemo2 {
}
@end
C++代码:
struct __CQTest__testDemo1_block_impl_0 {
struct __block_impl impl;
struct __CQTest__testDemo1_block_desc_0* Desc;
CQTest *self;
__CQTest__testDemo1_block_impl_0(void *fp, struct __CQTest__testDemo1_block_desc_0 *desc, CQTest *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
......
static void _I_CQTest_testDemo1(CQTest * self, SEL _cmd) {
void(*block)(void) = ((void (*)())&__CQTest__testDemo1_block_impl_0((void *)__CQTest__testDemo1_block_func_0, &__CQTest__testDemo1_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
static void _C_CQTest_testDemo2(Class self, SEL _cmd) {
}
-
block内调用self,CQTest *self;被捕获。 - 对象方法
testDemo1和类方法testDemo2都传递了self和 方法选择器_cmd。
对象方法 和 类方法 都会默认将self作为参数传递给方法内部,所以 self 是 局部变量。前面已经验证 局部变量 才会被 block 捕获。
block内使用 成员变量 和 实例属性 的区别
OC代码:
- (void)testDemo1 {
void(^block)(void) = ^{
NSLog(@"self.num = %@",self.num);
NSLog(@"_num = %@",self->_num);
};
block();
}
C++代码:
struct __CQTest__testDemo1_block_impl_0 {
struct __block_impl impl;
struct __CQTest__testDemo1_block_desc_0* Desc;
CQTest *self;
__CQTest__testDemo1_block_impl_0(void *fp, struct __CQTest__testDemo1_block_desc_0 *desc, CQTest *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __CQTest__testDemo1_block_func_0(struct __CQTest__testDemo1_block_impl_0 *__cself) {
CQTest *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_CQTest_4b6f9e_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("num")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_CQTest_4b6f9e_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_CQTest$_num)));
}
- 只捕获了
CQTest *self;。 -
self.num:调用了get方法,通过方法选择器获取属性的值。 -
_num:直接通过地址获取值。
一共有几种block?每种类型都是什么情况下生成的?
打印看下block的类型:
int main(int argc, char * argv[]) {
@autoreleasepool {
void(^block)(int ,int) = ^(int a, int b){
};
NSLog(@"\n %@ \n %@ \n %@ \n %@ \n",
[block class],
[[block class] superclass],
[[[block class] superclass] superclass],
[[[[block class] superclass] superclass] superclass]);
}
return 0;
}
输出日志:
__NSGlobalBlock__
__NSGlobalBlock
NSBlock
NSObject
这里打印的是__NSGlobalBlock类型,继承之NSBlock,但是最终还是继承之NSObject,再次证明block是OC对象。
前面的代码中我们看到impl.isa 指向的都是 _NSConcreteStackBlock 类对象地址。其实block的类型共三种:
- _NSConcreteGlobalBlock 全局静态。
- _NSConcreteStackBlock 保存在栈中。
- _NSConcreteMallocBlock 保存在堆中。
都是什么情况下生成的?
看段代码:
void (^block1)(void) = ^{
NSLog(@"block1");
};
int a = 10;
void (^block2)(void) = ^{
NSLog(@"block2-%d",a);
};
NSLog(@"\n block1:%@ \n block2:%@ \n block3:%@ \n",
[block1 class],
[block2 class],
[^{
NSLog(@"block3-%d",a);
} class]);
-
block1:内部没有调用外部变量。 -
block2:内部调用外部变量。 -
block3:内部调用外部变量,直接调用的block的class。
看下书输出日志:
block1:__NSGlobalBlock__
block2:__NSMallocBlock__
block3:__NSStackBlock__
block在内存中六大区域的位置
| 类型 | 描述 | 对应block类型 |
|---|---|---|
| 栈 | 存储局部变量,当其作用域执行完毕之后,就会被系统立即收回 | NSStackBlock |
| 堆 | 存储OC对象,手动申请的字节空间,需要手动释放 | NSMallocBlock |
| BSS段 | 未初始化的全局变量和静态变量,一旦初始化就会从BSS段中回收掉,转存到数据段中 | |
| 数据段 | 存储已经初始化的全局变量和静态变量,以及常量数据,直到结束程序时才会被立即收回 | NSGlobalBlock |
| 常量区 | 存放常量字符串,程序结束后由系统释放 | |
| 代码段 | 存放函数的二进制代码,内存区域较小,直到结束程序时才会被立即收回 |
-
__NSGlobalBlock__:存放在数据段,直到程序结束才会被回收,不过我们很少使用。 -
__NSStackBlock__:存放在栈区,由系统自动分配和释放,作用域执行完毕之后就会被立即释放。很少使用。 -
__NSMallocBlock__:存放在堆区,最常使用,存放在堆中需要我们自己进行内存管理。
__NSMallocBlock__调用了copy之后不会改变类型。
__NSStackBlock__调用了copy之后就会变成__NSMallocBlock__类型。
__NSMallocBlock__调用了copy之后引用计数会增加。
所以,在 MRC 环境下开发时,经常需要使用copy 将 block拷贝到堆中。即使栈上的block被销毁,堆上的block也不会被销毁,需要我们自己调用release操作来销毁。
而在ARC环境下系统会自动copy,block不会被销毁。
我们在ARC环境下定义全局的block属性时经常使用copy关键字,这是沿用了 MRC 环境下的书写风格,其实在ARC环境下使用copy和strong关键字是一样的。
ARC中在什么情况下系统会自动将block进行一次copy操作?
-
block作为函数的返回值时。
-
-
block被强指针引用时。
-
-
block作为Cocoa API中方法名含有usingBlock的方法参数时。
-
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }]
-
block作为GCD API的方法参数时。
-
dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
dispatch_block_t block);
block如何捕获 OC 对象?
前面我们捕获的除了self都是基本数据类型,下面研究下捕获OC对象的方式。
ARC环境下代码:
typedef void(^Block)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
Block block;
{
CQTest *test = [[CQTest alloc] init];
test.num = @"123";
block = ^{
NSLog(@"%@", test.num);
};
NSLog(@"%@", [block class]);//输出__NSMallocBlock__
}//test不会被释放
}//test被释放
return 0;
}
- 这里的
block类型是__NSMallocBlock__,存在堆区。
C++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
CQTest *test;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CQTest *_test, int flags=0) : test(_test) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
-
CQTest *test;看到了这句代码,说明强引用了test。
在ARC下,test在使用后并不会被立即释放。因为block代码块内了强引用test,系统会对block自动copy,block存到堆区。
上述代码如果在MRC下test就会被提前释放。因为这时的block存在栈区,不会强引用test。
__weak弱引用test:
int main(int argc, char * argv[]) {
@autoreleasepool {
Block block;
{
CQTest *test = [[CQTest alloc] init];
test.num = @"123";
__weak CQTest *weakTest = test;
block = ^{
NSLog(@"%@", weakTest.num);
};
NSLog(@"%@", [block class]);
}//test会被释放
}
return 0;
}
__weak修饰变量,需要告知编译器使用ARC环境及版本号否则会报错,添加说明-fobjc-arc -fobjc-runtime=ios-8.0.0
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
C++代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
CQTest *__weak weakTest;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CQTest *__weak _weakTest, int flags=0) : weakTest(_weakTest) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
CQTest *__weak weakTest = __cself->weakTest; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_415682_mi_1, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)weakTest, sel_registerName("num")));
}
-
CQTest *__weak weakTest;看到weakTest为弱引用。 -
test在作用域结束后被销毁。
再继续往下看C++代码:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakTest, (void*)src->weakTest, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakTest, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
- 我们发现多了两个函数
1、__main_block_copy_0:内部调用了_Block_object_assign函数,并且传递了原weakTest地址 和 目标weakTest地址 。
2、__main_block_dispose_0:内部带哦用了_Block_object_dispose函数,传递了原weakTest地址。 - 看到
copy和dispose中传递的都是block结构体本身__main_block_impl_0。
_Block_object_assign调用时机及作用:
当block进行copy操作的时候会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部再调用_Block_object_assign函数。
_Block_object_assign函数会自动根据__main_block_impl_0结构体内部对象的指针类型,对对象产生 强引用 还是 弱引用。
可以理解为_Block_object_assign函数内部会对对象test进行引用计数器的操作,如果__main_block_impl_0结构体内test指针是__strong类型,则为强引用,引用计数+1,如果指针是__weak类型,则为弱引用,引用计数不变。
_Block_object_dispose调用时机及作用:
当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。
_Block_object_dispose会对对象做释放操作,类似于release,也就是断开对对象的引用,而对象是否被释放还是取决于对象自己的引用计数。
总结:
1、
block捕获的变量为对象时,__main_block_desc_0结构体中会出现像个参数copy和dispose。block希望对捕获的对象进行内存管理。
2、block捕获的对象为auto时,如果block存储在栈区(此种情况为MRC下),不会对捕获的对象强引用。
3、一旦block被拷贝到堆上,copy函数会调用_Block_object_assign函数,根据捕获对象的指针类型(__strong,__weak,unsafe_unretained)进行 强引用 或者 弱引用。
4、一旦block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。
__block的作用
__block用于解决block内部不能修改auto变量值的问题,__block不能修饰 静态变量(static) 和 全局变量。
OC代码:
__block int num = 5;
Block block = ^{
num = 6;
NSLog(@"%d", num);
};
C++代码:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 5};
Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_dfce68_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
}
return 0;
}
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
int num;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_num_0 *num; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 自动生成了
__Block_byref_num_0类型的结构体。
1、__isa:__Block_byref_num_0本质上也是一个对象。
2、__forwarding:也是__Block_byref_num_0类型,存储的结构体自己的内存地址。
3、__size:__Block_byref_num_0所占用的内存空间。
4、num:真正存储的变量地方。 -
__main_block_impl_0结构体中并没有直接存储整型num,而是储着__Block_byref_num_0类型的结构体num。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself->num; // bound by ref
(num->__forwarding->num) = 6;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_25b6a2_mi_0, (num->__forwarding->num));
}
-
num->__forwarding->num:通过结构体num(__Block_byref_num_0类型)拿到自身的地址__forwarding(为了方便内存管理),再拿到我们在block外面定义的变量num。
__block将变量包装成一个结构体对象,然后再把变量存储在结构体里面。block内部存储对象指针,所以可以通过指针找到内存地址修改变量的值。
__block修饰对象:
__block CQTest *test = [[CQTest alloc] init];
Block block = ^{
NSLog(@"%@", test.num);
};
block();
C++代码:
struct __Block_byref_test_0 {// 48 共占用内存空间
void *__isa; // 8 内存空间
__Block_byref_test_0 *__forwarding; // 8 内存空间
int __flags; // 4 内存空间
int __size; // 4 内存空间
void (*__Block_byref_id_object_copy)(void*, void*); // 8 内存空间
void (*__Block_byref_id_object_dispose)(void*); // 8 内存空间
CQTest *test; // 8 内存空间
};
__attribute__((__blocks__(byref))) __Block_byref_test_0 test = {(void*)0,(__Block_byref_test_0 *)&test, 33554432, sizeof(__Block_byref_test_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((CQTest *(*)(id, SEL))(void *)objc_msgSend)((id)((CQTest *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CQTest"), sel_registerName("alloc")), sel_registerName("init"))};
Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_test_0 *)&test, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
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);
}
- 1、同样生成了一个结构体。并且结构体内部存储了对象
test。 - 2、多了两个函数
__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131 - 3、
__Block_byref_test_0占用的内存空间为48。src加40恰好指向的就为test指针。 - 4、
_Block_object_assign函数传入的是test地址
__block 和 __weak同时修饰对象:
OC代码:
CQTest *test = [[CQTest alloc] init];
__block __weak CQTest *weakTest = test;
Block block = ^{
NSLog(@"%@", weakTest.num);
};
block();
C++代码:
struct __Block_byref_weakTest_0 {
void *__isa;
__Block_byref_weakTest_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
CQTest *__weak weakTest;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakTest_0 *weakTest; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakTest_0 *_weakTest, int flags=0) : weakTest(_weakTest->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 系统自动生成的结构体
__Block_byref_weakTest_0依然是强引用。 - 结构体
__Block_byref_weakTest_0对内部的weakTest为弱引用。
但是在mrc环境下,尽管调用copy操作,__block结构体不会对test产生强引用,依然是弱引用。
__block修饰的变量内存管理:
1、当
block内存在栈上时,并不会对__block变量产生内存管理。
当blcok被copy到堆上时会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(相当于retain)。
2、当
block被copy到堆上时,block内部引用的__block变量也会被复制到堆上,并且持有变量,如果block复制到堆上的同时,__block变量已经存在堆上了,则不会复制。
而此时栈中的__Block_byref_test_0结构体中的__forwarding指针指向的就是堆中的__Block_byref_test_0结构体,堆中__Block_byref_test_0结构体内的__forwarding指针依然指向自己。
3、当
block从堆中移除的时,就会调用__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数,会自动释放引用的__block变量。
4、一旦使用
__block修饰的变量,__main_block_impl_0结构体内一律使用强指针引用生成的结构体。
对结构体内部变量的引用取决于我们在外部定义变量时的指针类型。
block循环引用问题
ARC环境下:
CQTest *test = [[CQTest alloc] init];
test.block = ^{
NSLog(@"%@", test.num);
};
-
test和block之间相互强引用,都不会被释放,内存泄漏。
解决方式:
1、使用__weak 、__unsafe_unretained修饰符可以解决循环引用的问题。
__weak不会产生强引用,指向的对象销毁时,会自动将指针置为nil。因此一般通过__weak来解决问题。__unsafe_unretained不会产生前引用,不安全,指向的对象销毁时,指针存储的地址值不变。
2、__block修饰符也可以解决循环引用的问题。
__block CQTest *test = [[CQTest alloc] init];
test.block = ^{
NSLog(@"%@", test.num);
test = nil;
};
test.block();
-
__block修饰变量会自动生成一个结构体__Block_byref_test_0。 -
test->block->__Block_byref_test_0->test三个对象形成了循环强引用。 -
test.block();调用后test被设置为nil,__Block_byref_test_0也就断开了对test的强引用,循环引用被断开,都可以被正常释放了。
__block解决循环引用的条件:1、必须执行block()。 2、block代码块内必须将test设置为nil
MRC环境下:
1、可通过__unsafe_unretained来解决问题,但是使用的问题跟ARC下相同。__weak在MRC下不能用。
2、使用__block来解决。在MRC下block即使手动调用了copy,自动生成的结构体对test依然是弱引用。所以可以解决循环引用的问题。
__strong 和 __weak
__weak typeof(self) weakSelf = self;
test.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@", strongSelf.num);
};
- 在
block内部重新使用__strong修饰self变量是为了在block内部有一个强指针指向weakSelf避免在block调用的时候weakSelf已经被销毁。