Block的本质
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block底层结构如下:
//block的底层结构
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//构造函数(C++)
__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;
}
};
//其中struct __block_impl结构
struct __block_impl {
void *isa;//isa指针
int Flags;
int Reserved;
void *FuncPtr;
};
PS:以上是通过clang指令后的cpp文件代码(截取主要代码):xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m-main.cpp 。其中main.m文件如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block1)(void) = ^{
NSLog(@"block1");
};
}
return0;
}
Block的基本声明和表现形式
1、关于声明
(1)在interface中声明Block时,有两种方法。如下的block1和block2:
typedef void(^BLOCK2)(void);
@interface ViewController ()
//第一种:直接声明,按照block的声明样式写就可以
@property (copy, nonatomic) void(^block1)(void);
//第二种:先将BLOCK2进行定义类型,然后再定义变量block2
@property (copy, nonatomic) BLOCK2 block2;
@end
(2)在MRC下,声明block使用copy修饰; 在ARC下,声明block使用strong和copy修饰都可以,一般建议使用copy。
2、表现形式(返回值或参数以int为例)
(1)无参数,无返回值
void(^block1)(void) = ^{
NSLog(@"block1");
};
(2)无参数,有返回值()
int(^block2)(void) = ^int{
NSLog(@"block2");
return 1
};
// 定义的时候,返回值可以省略,如下:
int(^block2)(void) = ^{
NSLog(@"block2");
return 1
};
(3)有参数,无返回值
void(^block3)(int) = ^(int a){
NSLog(@"block3---%d",a);
};
(4)有参数,有返回值
int(^block4)(int) = ^int(int a){
return a;
};
Block的变量捕获
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
1、局部变量
(1)auto变量类型
int main(int argc, const char * argv[]) {
@autoreleasepool {
// int age = 10;也即 auto int age = 10;只是默认去掉auto来展示
int age = 10;
// 定义block变量
void(^block)(void) = ^{
NSLog(@"block---%d",age);
};
int age = 20;
// 执行block内部代码
block();
}
return 0;
}
// 输出结果为:block---10
输出结果为何是block---10,而不是block---20呢? 首先我们通过clang命令来窥探一下底层的实现原理。
// 下面看一下main函数的底层结构(去掉没必要的干扰,简化后的)
int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool;
int age = 10;
// 定义block变量
void(*block)(void) = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
age);
int age = 20;
// 执行block内部代码
block->FuncPtr(block);
}
return 0;
}
// __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;
}
};
-
在__main_block_impl_0 结构体中可以看到,定义了一个同名变量age,用来接收传过来的值,其中age(_age)是将_age的值赋给age。PS: age(_age)是c++的写法,代表age=_age
-
main中,执行block内部代码,block内部通过调用__main_block_impl_0结构体的构造函数(其中传入的参数age = 10),从而使得结构体中的age赋值为10。仅仅只是值的传递,因此外面的age=20,只是改变外面的age,和__main_block_impl_0结构体内定义的age无关。
(2)static变量类型
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int age = 10;
// 定义block变量
void(^block)(void) = ^{
NSLog(@"block---%d",age);
};
int age = 20;
// 执行block内部代码
block();
}
return 0;
}
// 输出结果为:block---20
我们通过clang命令来窥探一下底层的实现原理:
// 下面看一下main函数的底层结构(去掉没必要的干扰,简化后的)
int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool;
static int age = 10;
// 定义block变量
void(*block)(void) = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
&age);
int age = 20;
// 执行block内部代码
block->FuncPtr(block);
}
return 0;
}
// __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;
}
};
- 在__main_block_impl_0 结构体中可以看到,定义了一个age的指针int *。
- main中,执行block内部代码,block内部通过调用__main_block_impl_0结构体的构造函数,将外面age的指针&age作为参数传入,并赋值给block内部定义的age指针。age和block里面的定义的age指向的是同一块内存,因此输出为20。
2、全局变量
int age = 10;
static int height = 190;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
void(^block)(void) = ^{
NSLog(@"age---%d",age);
NSLog(@"height---%d",height);
};
age = 20;
height = 170;
// 执行block内部代码
block();
}
return 0;
}
// 输出结果为:age---20,height---170
我们通过clang命令来窥探一下底层的实现原理:
int age = 10;
static int height = 190;
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;
}
};
int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool;
static int age = 10;
// 定义block变量
void(*block)(void) = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
&age);
age = 20;
height = 170;
// 执行block内部代码
block->FuncPtr(block);
}
return 0;
}
在__main_block_impl_0 结构体中可以看到,内部没有新定义age,或者height,说明block内部并没有捕获外部的全局变量。最后调用函数,打印的age和height是全局变量。
3、block访问外部变量的总结
Block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型,再往上是继承NSObject类型。
- NSGlobalBlock ( _NSConcreteGlobalBlock )
- NSStackBlock ( _NSConcreteStackBlock )
- NSMallocBlock ( _NSConcreteMallocBlock )
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
void(^block)(void) = ^{
};
NSLog(@"1 -- %@",[block class]);
NSLog(@"2 -- %@",[[block class] superclass]);
NSLog(@"3 -- %@",[[[block class] superclass] superclass]);
NSLog(@"4 -- %@",[[[[block class] superclass] superclass] superclass]);
}
return 0;
}
/*
输出结果为:
1 -- __NSGlobalBlock__
2 -- NSBlock
3 -- NSObject
4 -- (null)
*/
block的3种类型具体在哪些环境中体现呢?
| block类型 | 环境 |
|---|---|
| NSGlobalBlock | 没有访问auto变量 |
| NSStackBlock | 访问了auto变量 |
| NSMallocBlock | NSStackBlock类型调用了copy |
其中NSStackBlock只针对于MRC环境,因为ARC环境系统会自动帮我们做了一次copy操作。比如:
// 1、ARC环境下
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
int age = 10;
void(^block)(void) = ^{
NSLog(@"age----%d",age);
};
NSLog(@"blockClass---%@",[block class]);
}
return 0;
}
/* 输出的class结果为:blockClass---__NSMallocBlock__ */
// 2、MRC环境下(没有进行copy操作)
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
int age = 10;
void(^block)(void) = ^{
NSLog(@"age----%d",age);
};
NSLog(@"blockClass---%@",[block class]);
}
return 0;
}
/* 输出的class结果为:blockClass---__NSStackBlock__ */
// 3、MRC环境下(对block进行了copy操作)
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
int age = 10;
void(^block)(void) = [^{
NSLog(@"age----%d",age);
} copy];
NSLog(@"blockClass---%@",[block class]);
}
return 0;
}
/* 输出的class结果为:blockClass---__NSMallocBlock__ */
每一种类型的block调用copy后的结果如下:
| Block类型 | 副本源的配置存储域 | 复制的效果 |
|---|---|---|
| NSGlobalBlock | 程序的数据区域 | 什么也不做 |
| NSStackBlock | 栈 | 从栈复制到堆 |
| NSMallocBlock | 堆 | 引用计数增加 |
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
MRC下block属性的建议写法
- @property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
- @property (strong, nonatomic) void (^block)(void);
- @property (copy, nonatomic) void (^block)(void);
Block内部关于访问对象类型的auto变量
首先先创建一个MyPerson对象
// MyPerson.h
@interface MyPerson : NSObject
@property (assign, nonatomic) int age;
@end
// MyPerson.m
#import "MyPerson.h"
@implementation MyPerson
- (void)dealloc
{
// [super dealloc];
NSLog(@"MyPerson - dealloc");
}
@end
然后定义main函数中的block访问auto对象类型
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
MyBlock block;
{
MyPerson *person = [[MyPerson alloc] init];
person.age = 20;
block = ^{
NSLog(@"age----%d",person.age);
};
}
NSLog(@"blockClass---%@",[block class]);
}
return 0;
}
/*
打印的结果为:
blockClass---__NSMallocBlock__
MyPerson - dealloc
*/
从打印的结果,可以看出,block类型为__NSMallocBlock__。即存在于堆上。等block释放后MyPerson对象才释放,可以猜测到block对person对象进行了强引用。接下来我们通过clang指令后的cpp文件代码(截取主要代码)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
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};
- 可以看出__main_block_impl_0结构体中,捕抓了MyPerson *person变量(强指针)。而block为NSMallocBlock类型,位于堆上,可拥有person变量。而block里面的person变量指向MyPerson对象,因此,block没有销毁时,MyPerson对象不能自动销毁,只有block销毁了,MyPerson对象才能销毁;
- 而我们发现 __main_block_desc_0结构体多了void (*copy)和void (*dispose)两个函数指针,void (*copy)函数指针指向__main_block_copy_0函数,而void (*dispose)函数指针指向__main_block_dispose_0函数;
- 当block进行copy操作时,block内部会自动调用__main_block_copy_0函数,而__main_block_copy_0内部调用了_Block_object_assign,_Block_object_assign的作用是根据auto变量的强或者弱修饰符来做出相应操作,形成强引用或者弱引用;
- 当block从堆上移除时,block内部会调用__main_block_dispose_0函数中的_Block_object_dispose来释放auto变量。
对于弱引用,如下所示:
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
MyBlock block;
{
__weak MyPerson *person = [[MyPerson alloc] init];
person.age = 20;
block = ^{
NSLog(@"age----%d",person.age);
};
}
block();
NSLog(@"blockClass---%@",[block class]);
}
return 0;
}
/*
断点在block(),运行到断点处,打印了:Person - dealloc。说明person在block没有销毁前就已经提前释放了,所以block没有强引用auto变量。
*/
// 通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-15.0.0 main.m(兼容带有__weak的转换)转换成cpp文件代码。提取出不一样的地方如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyPerson *__weak person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__weak _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
看出,加__weak后,__main_block_impl_0中的person变量变成了__weak修饰。
当我们切换到MRC环境:
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
MyBlock block;
{
MyPerson *person = [[MyPerson alloc] init];
person.age = 20;
block = ^{
NSLog(@"age----%d",person.age);
};
[person release];
}
block();
NSLog(@"blockClass---%@",[block class]);
}
return 0;
}
断点到block()这一行,运行到断点时,打印MyPerson - dealloc,说明person对象提前被释放了。此时打印blockClass的类型为__NSStackBlock__,得出block是在栈上,不会对auto变量强引用。所以block还没释放前,auto变量已提前释放。
如果在MRC环境对block实行copy操作会是什么情况呢?如下:
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义block变量
MyBlock block;
{
MyPerson *person = [[MyPerson alloc] init];
person.age = 20;
block = [^{
NSLog(@"age----%d",person.age);
} copy];
[person release];
}
block();
[block release];
NSLog(@"blockClass---%@",[block class]);
}
return 0;
}
同样断点到block()这一行,运行到断点时,person不会释放,只有释放block后,auto变量才会被释放。打印的blockClass为__NSMallocBlock__。这说明block执行copy操作后,被拷贝到了堆上,而block指向对象是有强指针引用的,因此会默认对其进行copy操作,将block指向的对象存放在堆区,因此是可以拥有其内部的变量person。
总结:当block内部访问了对象类型的auto变量时
-
如果block是在栈上,将不会对auto变量产生强引用
-
如果block被拷贝到堆上
- 会调用block内部的copy函数
- 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)
- 当栈上的block复制到堆时,调用copy函数;当堆上的block被废弃时,调用dispose函数
Block的__block修饰符
__block可以用于解决block内部无法修改auto变量值的问题, 但不能修饰全局变量、静态变量(static)。
__block int age = 10;
MyBlock block = ^{
age = 20;
NSLog(@"age---%d",age);
};
block();
过clang指令后的cpp文件代码(截取主要代码)
// __Block_byref_age_0结构体
struct __Block_byref_age_0 {
void *__isa; //isa指针,代表该类型是一个对象
__Block_byref_age_0 *__forwarding; //接收自己的地址
int __flags;
int __size; //改类型值的大小
int age; //__block修饰的变量age=10
};
// __main_block_impl_0新创建了一个__Block_byref_age_0类型的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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_m2_p61bk9fj11106mwvj765gjbr0000gn_T_main_469f18_mi_0,(age->__forwarding->age));
}
// 从以下简化后的main函数中,我们可以清楚看到
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// __block int age = 10
__Block_byref_age_0 age = {
0,
&age,
0,
sizeof(__Block_byref_age_0),
10};
// 定义block
MyBlock block = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
&age,
570425344);
// 调用block
block->FuncPtr(block);
}
return 0;
}
- 上面可以看出,block并没有直接捕获age值,而是编译器将__block变量包装成一个类型为__Block_byref_age_0的对象。
- main函数中__block int age = 10变成了__Block_byref_age_0结构体,将&age(定义好的结构体变量的地址即自己的地址)传给了结构体中的__forwarding,将sizeof(__Block_byref_age_0)自己的大小传给了结构体中的size,将10传给了结构体中的age。
- 定义block中,&age(block外面定义的age的地址)传给了定义的__main_block_impl_0构造函数中的__Block_byref_age_0,使得block内部有个__Block_byref_age_0 *age指针指向结构体__Block_byref_age_0,而__Block_byref_age_0结构体内部又有个age的成员存储了10的值。
- 当修改age值时,找到block内部函数__main_block_func_0,(age->__forwarding->age) = 20;通过age拿到__forwarding,也就是拿到自己的指针地址,然后再通过地址拿到结构体里面的age,从而实现了修改。
__block的内存管理
- 当block在栈上时,并不会对__block变量产生强引用。
- 当block被copy到堆时
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会对__block变量形成强引用(retain)
当在栈上两个block(分别命名为block1和block2)同时引用一个__block修饰的变量时,对block1进行copy操作后,block1会从栈复制到堆上面,同时,会将block1内部访问的__block修饰的变量也复制到堆上面,在堆上面的block1还是会对堆上面的__block修饰的变量进行强引用。如果block2也进行copy操作,拷贝到堆上,因为__block修饰的变量已经拷贝到了堆上了,就不用重复拷贝,只需要把堆上的block1也强引用堆上__block修饰的变量就可以了。
- 当block从堆中移除时
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的__block变量(release)
当只有一个堆上的block对堆上的__block修饰的变量进行强引用,block进行dispose操作后,block会废弃。__block修饰的变量没有了强引用指向,也会被释放掉。 如果是两个堆上的block同时对堆上的__block修饰的变量进行强引用,等两个block进行dispose操作后分别废弃掉后,__block修饰的变量没有了强引用指向,也会被释放掉。
__block的__forwarding指针
为什么不直接用age,而使用age->__forwarding->age的方式来访问?
- 如果我们访问的age变量在栈上的时候,可以直接访问age,但是如果age变量被拷贝到堆上,直接访问,还是访问到栈上的age,这样就出问题了;(复制并不是剪切,栈上面是还有的。)
- 通过__forwarding找到它自己,然后访问里面的age,在栈上也很容易找到age,这是行的通的,虽然复杂了一步,但是age变量拷贝到堆上后,__forwarding指针会指向堆上面的__Block_byref_age_0结构体,这样里面访问的__block修饰的变量,确保了是堆上面的。
__block修饰的对象类型
-
当__block变量在栈上时,不会对指向的对象产生强引用
-
当__block变量被copy到堆时
- 会调用__block变量内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
- 如果__block变量从堆上移除
- 会调用__block变量内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放指向的对象(release)
Block的关于循环引用
循环引用问题
看如下代码:
// MyPerson.h
typedef void(^MyBlock)(void);
@interface MyPerson : NSObject
@property (assign, nonatomic) int age;
@property (copy, nonatomic) MyBlock block;
@end
// MyPerson.m
#import "MyPerson.h"
@implementation MyPerson
- (void)dealloc
{
// [super dealloc];
NSLog(@"MyPerson - dealloc");
}
@end
// person声明了一个block变量
main.m如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyPerson *person = [[MyPerson alloc] init];
person.age = 20;
person.block = ^{
NSLog(@"age----%d",person.age);
};
NSLog(@"------");
}
return 0;
}
程序运行结束只打印:------。person并没有释放,造成了内存泄露(循环引用)。
循环引用原因
接下来我们结合.cpp文件分析一下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
由MyPerson *__strong person,可以看出block内部其实是有一个强指针引用了MyPerson类型的person对象,指向着MyPerson;而MyPerson中定义的_block的成员变量,强指针指向着block。程序运行结束,局部变量person被销毁了,但是MyPerson和block还被互相强引用着,没办法销毁,从而造成了循环引用。
如何解决循环引用
- 通过__weak来解决
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyPerson *person = [[MyPerson alloc] init];
person.age = 20;
__weak MyPerson *weakPerson = person;
person.block = ^{
NSLog(@"age----%d",weakPerson.age);
};
}
return 0;
}
// 程序运行结束打印了:MyPerson - dealloc
通过其.cpp文件可以看出block内部有一个弱指针引用了MyPerson类型的person对象
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
由于block内部指向MyPerson的为弱指针,当局部变量person被销毁,因为没有强引用,MyPerson对象也跟着销毁,从而解决了循环引用。
- 通过__unsafe_unretained来解决
__unsafe_unretained解决循环引用也类似于__weak,这里就不做过多的叙述,不过值得注意的是:
- __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
- __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针储存的地址值不变
- 通过__block来解决
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block MyPerson *person = [[MyPerson alloc] init];
person.age = 20;
person.block = ^{
NSLog(@"age----%d",person.age);
person = nil;
};
person.block();
}
return 0;
}
// 也会输出:MyPerson - dealloc
再看一下.cpp文件
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
MyPerson *__strong person;
};
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;
}
};
- 可以看出存在三个对象,一个是__block包装成的对象(__Block_byref_person_0),一个是block对象,一个是MyPerson对象;
- block对象对__Block_byref_person_0对象产生强引用,而__Block_byref_person_0对象对MyPerson对象产生强引用,MyPerson对象对block对象产生强引用;
- 当执行到
person = nil,__Block_byref_person_0对象对MyPerson对象产生强引用会断开,没了引用,person执行完就会被销毁,从而解决了循环引用问题。
值得注意的是:person.block();必须调用一次,为了执行person = nil;。
4、MRC应该注意 在MRC下,可以使用__unsafe_unretained解决循环引用。因为MRC没有__weak,也就不存在使用__weak解决循环引用的问题了。也可以使用__block解决循环引用问题。