block的本质问题
- block的基本使用在这里就不说了,
- 将oc的代码转换成c或者c++代码的命令(比如我转换的main.m文件)
首先切换到main.m所在的文件位置,然后执行这段命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
如果代码中有__weak
执行这段命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
下面是我执行代码截图:
image.png
下面我们来看下block的本质是什么?
// __block_impl的结构
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// __main_block_desc_0 什么东西
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
// 内部函数调用
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
解释:其中__main_block_impl_0是整个block的函数结构,__block_impl装着:
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
isa 指向对象的指针,flag标示 还有Reserved预留参数,FuncPtr是内部的具体指向的代码。由此可以看出 它的本质是一个oc对象。
__main_block_desc_0 装着:
size_t reserved;
size_t Block_size;
其中reserved预留参数,Block_size是这个block占用内存的大小。
其中
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这在c++中叫构造函数,相当于在初始化之前进行付值。
- 下面代码这样写,看看底层怎么编译的
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^myBlcok)(void) = ^{
NSLog(@"age = %d",age);
};
age = 20;
myBlcok();
}
return 0;
}
打印的结果是 10 我们这个毫无疑问,age 这里面是局部变量,block内部参与编译的 具体底层的执行代码如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
void (*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;
((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
}
return 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;
}
};
可以看到,age直接进行的值传递
image.png
这就是为什么age是10而不是20,因为block在写的时候直接将age参与编译,进行值传递,局部变量。
- 如果我们使用下面这种情况:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
static int weight = 10;
void (^myBlcok)(void) = ^{
NSLog(@"age = %d, weight = %d",age,weight);
};
age = 20;
weight = 20;
myBlcok();
}
return 0;
}
可以看到底层的执行的代码是:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
static int weight = 10;
void (*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &weight));
age = 20;
weight = 20;
((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
}
return 0;
}
结构体为:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *weight;
__main_block_impl_0(void *fp, struct __main_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;
}
};
可以看到weight都参与编译了,但是weight与age不同的是weight传递的是地址,而age直接传递的是值,所以weight打印的是20,其实主要是static作用,static一个作用是:分配独立的内存空间,一直存在的 所以block内部直接传递地址,这样才符合我们的逻辑。
- 下面是这样情况:
int height = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
static int weight = 10;
void (^myBlcok)(void) = ^{
NSLog(@"age = %d, weight = %d ,height = %d",age,weight,height);
};
age = 20;
weight = 20;
height = 30;
myBlcok();
}
return 0;
}
底层的代码为:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
static int weight = 10;
void (*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &weight));
age = 20;
weight = 20;
height = 30;
((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
}
return 0;
}
结构体为:
int height = 10;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *weight;
__main_block_impl_0(void *fp, struct __main_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不参与编译。
局部变量,blcok参与编译。
- block的有参数的情况是:
如下面的例子:
void (^myBlock)(int,int) = ^(int a,int b){
NSLog(@"a = %d -- b = %d",a,b);
};
myBlock(10,10);
底层的代码实现为:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*myBlock)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 10);
}
return 0;
}
她的结构体为:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到直接将值进行传递过去,和不穿参数的差不多,只不过他将参数进行了传递。
- block的类型,block分为三种类型 分别为:NSGlobalBlock、NSStackBlock、NSMallocBlock,他们分别对应应用程序的那个区域:
image.png
可以看到:NSGlobalBlock 主要对应于 data区:(这块区域主要是用来存储全局变量的)NSStackBlock 主要对应于:栈区(这个区域主要是用来存储局部变量的,系统自动分配内存进行处理,先进后出),NSMallocBlock主要是对应于堆区(主要是一般创建对象,程序员可以进行管理内存分配的区域)。 - 什么情况是NSGlobalBlock
代码1如下:
void (^myBlock)(void) = [^{
NSLog(@"-------------------------------------");
} copy];
myBlock();
NSLog(@"%@",[myBlock class]);
image.png
代码2如下:
void (^myBlock)(void) = ^{
NSLog(@"---------------height = %d----------------------",height);
};
myBlock();
NSLog(@"%@",[myBlock class]);
image.png
可以看到 没有引用auto的block 默认情况下生成的都是 NSGlobalBlock
-
NSGlobalBlock的继承关系 以及他的父类:
image.png - NSStackBlock的产生情况,首先要在编译器的build setting中 搜索 automatic re 将xcode修改为非arc。
代码1如下:
int age = 10;
void (^myBlock)(void) = ^{
NSLog(@"---------------age = %d----------------------",age);
};
myBlock();
NSLog(@"%@+++++%@++++++++%@++++++%@",[myBlock class],[[myBlock class] superclass],[[[myBlock class] superclass] superclass],[[[[myBlock class] superclass] superclass] superclass]);
image.png
可以看到NSStackBlock 是在引用了 auto的情况下 出现的,其中NSStackBlock的继承关系如上图。
- NSMallocBlock的产生情况:它是由NSStackBlock 的copy产生的操作。
代码1如下:
int age = 10;
void (^myBlock)(void) = [^{
NSLog(@"---------------age = %d----------------------",age);
}copy];
myBlock();
NSLog(@"%@+++++%@++++++++%@++++++%@",[myBlock class],[[myBlock class] superclass],[[[myBlock class] superclass] superclass],[[[[myBlock class] superclass] superclass] superclass]);
image.png
代码2如下:
int age = 10;
void (^myBlock)(void) = [^{
// NSLog(@"---------------age = %d----------------------",age);
}copy];
myBlock();
NSLog(@"%@+++++%@++++++++%@++++++%@",[myBlock class],[[myBlock class] superclass],[[[myBlock class] superclass] superclass],[[[[myBlock class] superclass] superclass] superclass]);
image.png
- 得出结论:没有引用auto的block默认情况下 产生的是NSGlobalBlock 引用了auto的block 产生的是NSStackBlock,NSStackBlock调用了copy产生的是NSMallocBlock,NSGlobalBlock的copy操作还是NSGlobalBlock
- 特殊情况说明:
看如下的代码:
void (^TestBlock)(void);
void test(){
int weight = 10;
TestBlock = ^{
NSLog(@"weight = %d",weight);
};
}
int height = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
TestBlock();
}
return 0;
}
image.png
- 可以看到并不是我们想要知道的10,它是一个随机的数字,原因是这个blcok是一个NSStackBlock 她在非arc的情况下 作用区域在栈上,test函数执行过,他已经消失,所以他随便执行的操作。解决的办法是进行copy操作,是他的作用区域由栈上变到堆上,也就是变成NSMallocBlock.
- block的copy操作,什么情况下缠身copy(当前前提是在arc的环境下)
1.返回是block的block 进行了copy操作。
代码如下:
typedef void (^TestBlock)(void);
TestBlock test(){
int age = 3;
return ^{
NSLog(@"-------------%d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestBlock testBlock = test();
NSLog(@"class ++++ %@",[testBlock class]);
}
return 0;
}
image.png
解释:按道理将应该是 返回的是NSStackBlock 但是返回的是NSMallocStack原因是:在arc的情况下自动进行了copy了。
- 强引用block的情况下,也对block进行了copy操作。
代码如下:
typedef void (^TestBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 3;
TestBlock testblock = ^(){
NSLog(@"age = %d",age);
};
testblock();
NSLog(@"class ++++ %@",[testblock class]);
}
return 0;
}
image.png
解释:按道理将应该是 返回的是NSStackBlock 但是返回的是NSMallocStack原因是:在arc的情况下自动进行了copy了。
- 还有就是使用了blcok 的cocoaAPI方法中含有 usingBlock作为参数时,例如:
代码如下:
NSArray *array = [NSArray array];
[array sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
}];
- block 作为方法参数时:
代码如下:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
- 对象类型的auto变量:
- 如果block的作用域作用在栈上,那么内部调用外部对象的auto变量的时候不会对外部产生强引用。
代码1:
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock myBlock;
{
DGPerson *person = [[DGPerson alloc] init];
person.age = 20;
myBlock = ^{
NSLog(@"age = %d",person.age);
};
[person release];
}
NSLog(@"--------------");
}
return 0;
}
我们都知道这个block 是NSStackBlock类型的,所以是作用在栈上的,那么即使person对象是强引用的,block也不会对它强引用。
image.png
代码2:如果我们block 进行了copy操作, 那就是把block从栈上copy到堆上,那它就变成了NSMallocBlock ,如下代码:
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock myBlock;
{
DGPerson *person = [[DGPerson alloc] init];
person.age = 20;
myBlock = [^{
NSLog(@"age = %d",person.age);
} copy];
[person release];
}
NSLog(@"--------------");
}
return 0;
}
执行结果为:
image.png
可以看到block变成NSMallocBlock 的话,person对象不会释放 需要等到block执行完了才会释放。(执行blcok() 将其释放)。
具体例子证明:
2.如果block作用在堆上,那么block内部调用外部对象的auto变量时候,如果外部对象是请引用的那么block对它产生强引用,如果外部对象是若引用的block会对它产生若引用。
具体例子证明:
代码1:
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock myBlock;
{
DGPerson *person = [[DGPerson alloc] init];
person.age = 20;
myBlock = ^{
NSLog(@"age = %d",person.age);
};
}
NSLog(@"--------------");
}
return 0;
}
image.png
可以看到先执行的是打印 后来person对象才销毁(当前前提是在arc的环境下)
说明block对它产生的强引用,如果是若引用 那么person对象应该是先销毁。
- 代码二:
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock myBlock;
{
DGPerson *person = [[DGPerson alloc] init];
person.age = 20;
__weak DGPerson *weakPerson = person;
myBlock = ^{
NSLog(@"age = %d",weakPerson.age);
};
}
NSLog(@"--------------");
}
return 0;
}
image.png
可以看到person对象是先销毁了才执行的打印。
3.具体看下,如果block 从栈上copy到堆上 执行了,我将代码转换成了c++代码(使用命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m):
image.png
可以看到强引用多了copy 和dispose方法。
代码(强引用的):
typedef void(*MyBlock)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
DGPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, DGPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
DGPerson *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1p_2n5k1fqd4fjd7bg4vz_38qmc0000gn_T_main_8f3440_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock myBlock;
{
DGPerson *person = ((DGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((DGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("DGPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 20);
myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1p_2n5k1fqd4fjd7bg4vz_38qmc0000gn_T_main_8f3440_mi_1);
}
return 0;
}
代码(弱引用的):
首先会报这个错误:
image.png
(使用这个命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m)
typedef void(*MyBlock)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
DGPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, DGPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
DGPerson *__weak weakPerson = __cself->weakPerson; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1p_2n5k1fqd4fjd7bg4vz_38qmc0000gn_T_main_4d3bc6_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock myBlock;
{
DGPerson *person = ((DGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((DGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("DGPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 20);
__attribute__((objc_ownership(weak))) DGPerson *weakPerson = person;
myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakPerson, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1p_2n5k1fqd4fjd7bg4vz_38qmc0000gn_T_main_4d3bc6_mi_1);
}
return 0;
}
可以看到区别是:
image.png
image.png
copy操作首先调用的_Block_object_assign这个函数,这个函数会根据auto变量的修饰符(__strong 、__weak 、__unsafe__retain)进行产生强引用或者若引用。
如果block从堆上移除,它会内部调用_Block_object_dispose方法,这个方法会堆block进行release操作。
-
__block的作用
作用:使block内部能够改变auto变量的值,但是__block修改全局变量和static变量。
将main.m文件转化成c++文件,可以看到底层的代码为:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
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;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
可以看到将__block的转化为对象了,__Block_byref_age_0 *age; 进行了指针的付值,所以能够修改了。
下面看一个这个现象:
代码如下:
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age; // by ref
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
void (^myBlock)(void) = ^(){
age = 20;
NSLog(@"age = %d",age);
};
myBlock();
// 将block 替换成我们写的那个结构体
struct __main_block_impl_0 *block = (__bridge struct __main_block_impl_0 *)myBlock;
NSLog(@"age 的 address = %p",&age);
}
return 0;
}
我们分析一下这个age是那个age ,首先可以看到
image.png
也就是说当前的age不是我们的age,那block中的age是哪个呢?我们来分析一下:
image.png
image.png
可以分析得到:age就是__struct_byref_age_0 中的age
另外我们打印一下 看一下结果呢
image.png
可以看到age确实是我们分析的age
-
__block的内存管理问题
当block作用在栈上时,block不会对__block产生强引用。当block从栈上copy到堆上时,block内部会对__block产生强引用。
我们来看一个例子和源码:
代码1:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
NSObject *object = [[NSObject alloc] init];
void (^myBlock)(void) = ^(){
age = 20;
NSLog(@"object = %@ ---age = %d",object,age);
};
myBlock();
}
return 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->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);}
由此可见:block从栈copy到堆上,会进行调用copy函数 ,copy函数又调用_Block_object_assign函数,这个函数对__block产生强引用。(值得注意的是:现在说对__block产生强引用还证据不足,暂时这么说着)
当block从堆中移除的时候,会调用dispose方法,这个方法内部会调用_Block_object_dispose函数,这个函数会自动释放__block。
下面来看研究一下__forwarding的作用:
因为底层代码的有这么一句(当我们用__block 修饰age的时候)
(age->__forwarding->age) = 20;
这个__forwarding有什么作用呢?
作用:当我们的blcok在栈上的时候,那么这个__forwarding自然指向的就是栈上的block ,那么当block copy到堆上,那么这个__forwarding指向的就是堆上的block了,可以通过这段代码看出:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
}
发现__forwarding又是自身的结构体,这样的好处是我们无论什么时候调用age(以此为例子)那么指向都是我们想要的age
下面的一幅图更好的证明了这个:
image.png
-
__block修饰对象:
代码1:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block DGPerson *person = [[DGPerson alloc] init];
void (^myBlock)(void) = ^(){
person.age = 20;
NSLog(@"age = %d",person.age);
};
myBlock();
}
return 0;
}
对应的底层代码为:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
代码2:
int main(int argc, const char * argv[]) {
@autoreleasepool {
DGPerson *person = [[DGPerson alloc] init];
__block __weak DGPerson *weakPerson = person;
void (^myBlock)(void) = ^(){
person.age = 20;
NSLog(@"age = %d",weakPerson.age);
};
myBlock();
}
return 0;
}
对应的底层代码:
struct __Block_byref_weakPerson_0 {
void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
DGPerson *__weak weakPerson;
};
由此可以看出:__block 在堆上 一直是被强引用的,当它修饰的对象为若引用的时候 ,_Block_object_assign会对对对象产生若引用,反之为强引用,但是block对__block产生的一直都是强引用。
-
block的循环引用问题
下面的代码为:
DGPerson *person = [[DGPerson alloc] init];
person.myBlock = ^{
person.age = 20;
};
person.myBlock();
NSLog(@"-------------");
代码执行结果为:
image.png
可以看到没有执行dealloc方法 ,按道理将我们打括号执行完毕,对象该销毁但是没有销毁那就是循环引用了,为什么 我们将main.m转换成c++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
DGPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, DGPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
然后我们知道DGPerson 强引用block,
@interface DGPerson : NSObject
@property (assign, nonatomic) int age;
@property (strong, nonatomic) void(^myBlock)(void);
@end
所以结构图为:
image.png
这样我们的解决办法是__weak ,但是我们把那个线变成弱的呢,我们应该把绿色箭头变成绿色的,因为person 我们需要strong或者copy,好让他作用在堆上 我们可以控制他的生命周期,所以正确的解决办法是:
DGPerson *person = [[DGPerson alloc] init];
__weak typeof(person)weakPerspon = person;
person.myBlock = ^{
weakPerspon.age = 20;
};
NSLog(@"-------------");
image.png
其中还有一个办法就是:把__weak 换成 __unsafe_unretained
DGPerson *person = [[DGPerson alloc] init];
__unsafe_unretained typeof(person)weakPerspon = person;
person.myBlock = ^{
weakPerspon.age = 20;
};
NSLog(@"-------------");
image.png
可以看到 也能解决循环引用的问题 ,但是这样做是不安全的,因为__weak
是我们这个对象调用完毕的时候,或者说离开作用域的时候他会自动将对象设置为nil,而__unsafe_unretained 他不会设置为nil,也就是内存已经回收了,但是他还是指着那个内存空间,这样就可能造成野指针,所以不建议使用。
还有我们用__block 也可以解决这个问题
__block DGPerson *person = [[DGPerson alloc] init];
person.myBlock = ^{
NSLog(@"age = %d",person.age);
person = nil;
};
person.myBlock();
NSLog(@"-------------");
可以看到__block 必须执行myBlock()和将对象设置为nil,这样的话虽然能够解决 但是还是比较麻烦,所以在arc的情况下最好的解决办法就是__weak
- 在非arc的情况下 解决循环引用问题:(可以不看)
DGPerson *person = [[DGPerson alloc] init];
__unsafe_unretained typeof(person) weakPerson = person;
person.myBlock = [^{
NSLog(@"age = %d",weakPerson.age);
} copy];
person.myBlock();
[person release];
NSLog(@"-------------");
image.png
因为mrc 情况都是手动管理内存,所以__unsafe_unretained没事。
完毕!!!!!!!!!!!!!