- 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
已经被销毁。