1、循环引用的现象
- (void)viewDidLoad {
[super viewDidLoad];
{
MJPerson *person = [[MJPerson alloc] init];
person.age = 12;
person.block = ^{
NSLog(@"person's age is %d",person.age);
};
}
NSLog(@"离开了MJPerson的作用域,MJPerson应该要释放掉");
}
================================================
打印结果:
//(并没有打印person---dealloc);
BlockTest[942:23896] 离开了MJPerson的作用域,MJPerson应该要释放掉
像上面例子中一样,当离开了MJPerson的作用域之后,MJPerson本该被释放却没有被释放掉,这种现象就是由于产生了block的循环引用而导致的内存泄漏。
由于这个perosn中持有block,block中又强引用了person,所以当执行完毕的时候,person和person中的block互相引用,没有办法释放就导致了这种现象。
2、如何解决循环引用
在之前学习的时候,我们就了解到用__weak可以避免block对对象产生强引用。
同样使用__unsafe__unretained修饰也可以
使用__block也能达到效果。
方法一:
- (void)viewDidLoad {
[super viewDidLoad];
{
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
//__weak typeof(person) *weakPerson = person;实际中,我们常用typeof来确定类型
__weak MJPerson *weakPerson = person;
person.block = ^{
NSLog(@"person's age is %d",weakPerson.age);
};
}
NSLog(@"离开了MJPerson的作用域,MJPerson应该要释放掉");
}
================================================
打印结果:
BlockTest[1718:65438] person---dealloc
BlockTest[1718:65438] 离开了MJPerson的作用域,MJPerson应该要释放掉
方法二:
- (void)viewDidLoad {
[super viewDidLoad];
{
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
__unsafe_unretained MJPerson *weakPerson = person;
person.block = ^{
NSLog(@"person's age is %d",weakPerson.age);
};
}
NSLog(@"离开了MJPerson的作用域,MJPerson应该要释放掉");
}
================================================
打印结果:
BlockTest[1718:65438] person---dealloc
BlockTest[1718:65438] 离开了MJPerson的作用域,MJPerson应该要释放掉
方法三:
- (void)viewDidLoad {
[super viewDidLoad];
{
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
__block MJPerson *blockPerson = person;
person.block = ^{
NSLog(@"person's age is %d",blockPerson.age);
blockPerson = nil;
};
//调用block
person.block();
}
NSLog(@"离开了MJPerson的作用域,MJPerson应该要释放掉");
}
================================================
打印结果:
BlockTest[1827:71936] person's age is 10
BlockTest[1827:71936] person---dealloc
BlockTest[1827:71936] 离开了MJPerson的作用域,MJPerson应该要释放掉
从上面的三段代码中,我们也可以看出这几种方法都可以解决block的循环引用问题,但是他们还是有些区别的。
- <1>其中,用__weak、__unsafe__unretained修饰都是使block对对象的引用变成弱引用来解决。
但是使用中推荐__weak,使用__unsafe__unretained是不安全的,它们的区别是:
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe__unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
-
<2>使用__block修饰的道理是将两者间的双向强引用变成三者间的引用
但使用__block修饰是必须在调用了block之后,才会释放对象,而且写法上需要注意的是,在block代码块中必须将对象置空。
对比几种方法,还是使用最__weak来解决循环引用的问题最好。
3、拓展:MRC下怎么解决
方法一:
// MRC环境下不支持__weak,但是可以使用__unsafe_unretained
// __unsafe_unretained就是不会对person进行retain操作
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
__unsafe_unretained MJPerson *weakPerson = person;
person.block = ^{
NSLog(@"person's age is %d",weakPerson.age);
};
[person release];
方法二:
// 因为MRC下,__block不会修饰,其底层的copy函数中的_Block_object_assign不会对其进行retain操作
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
__block MJPerson *blockPerson = person;
person.block = ^{
NSLog(@"person's age is %d",blockPerson.age);
//这里也不需要对person进行置空操作了
};
[person release];
4、__weak修饰后,再用__strong修饰的问题
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc] init];
student.name = @"Hello World";
__weak typeof(student) weakStu = student;
student.studyBlock = ^{
//问:这里为什么要进行一个这样的操作
//答:你用一个强指针可以确保student不会死掉,再者这里再转成strong类型可以骗过编译器
Student *strongStu = weakStu;
//在block里面用强引用 和 在block外面用强引用 是不一样的
// student是block外面的强引用,是持有这个block的对象变量
// strongStu,我们应该把他看成是个临时变量,在block结束后会自动销毁掉。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//这个strongStu指向的地址还是student的地址,而且因为自己强引用了studentd对象,所以student对应的引用计数加一
//导致在block执行结束之前,student不会被释放,等到执行了之后,过了这个作用域,strongStu释放了,student也就释放了
NSLog(@"my name is = %@", strongStu.name);
});
};
student.studyBlock();
}