ARC模式很方便的使程序员可以不再关心是否通过retain/release正确地释放每个使用过的对象,而将更多的精力用于开发程序的核心功能,但是并不是使用了ARC就可以高枕无忧,在引用的时候还要注意引用的方式,特别是在块引用中,避免循环引用问题。
一、循环引用的例子
使用引用计数,必须要注意循环引用的问题。环形相互引用的多个对象,将会导致内存泄露,因为循环中的引用计数不会降为0.
Object A->Object B:
Object B->Object C:
Object C->Object A:
这种情况下,就算想将A、B、C都释放掉,但按照规则,也只有等到C释放掉之后才有可能释放掉A(否则A的引用计数为1,无法被释放),同样,B释放之后才能释放C,而B释放需要先将A释放,这就形成了循环引用,结果是三个对象都不会被释放,这样就造成了内存泄露。
二、强引用和弱引用
2.1、强引用
一般情况下,当默认创建一个对象的参数的时候,我们都会习惯使用strong
的关键词,比如
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (strong,nonatomic) NSString *name;
@property (strong,nonatomic) Person *person;
@end
这样在引用的时候,这个person变量就是强引用,如果使用下面的代码声明两个Person的对象,然后将bPerson赋值给aPerson.person,那么就是在强引用bPerson,所以打印引用计数是2
- (void)viewDidLoad {
[super viewDidLoad];
Person *aPerson = [[Person alloc] init];
aPerson.name = @"hu";
NSLog(@"%@,%lu",aPerson.name,CFGetRetainCount((__bridge CFTypeRef)aPerson));
Person *bPerson = [[Person alloc] init];
bPerson.name = @"dong";
NSLog(@"%@,%lu",bPerson.name,CFGetRetainCount((__bridge CFTypeRef)bPerson));
NSLog(@"========================");
aPerson.person = bPerson;
NSLog(@"%@,%lu",aPerson.name,CFGetRetainCount((__bridge CFTypeRef)aPerson));
NSLog(@"%@,%lu",bPerson.name,CFGetRetainCount((__bridge CFTypeRef)bPerson));
NSLog(@"========================");
bPerson = nil;
NSLog(@"%@,%@",aPerson.person.name,bPerson.name);
NSLog(@"========================");
[self initButton];
}
打印结果
2017-04-22 14:17:01.644 arcTest[13163:2976579] hu,1
2017-04-22 14:17:01.644 arcTest[13163:2976579] dong,1
2017-04-22 14:17:01.644 arcTest[13163:2976579] ========================
2017-04-22 14:17:01.644 arcTest[13163:2976579] hu,1
2017-04-22 14:17:01.645 arcTest[13163:2976579] dong,2
2017-04-22 14:17:01.645 arcTest[13163:2976579] ========================
2017-04-22 14:17:01.645 arcTest[13163:2976579] dong,(null)
2017-04-22 14:17:01.645 arcTest[13163:2976579] ========================
在aPerson.person = bPerson;调用之后,bPerson的引用计数为2,就是初始化一次,然后又被引用了一次。但是当bPerson为nil的时候,在aPerson.person中,还是有bPerson的name,并没有被释放掉。
2.1、弱引用
将strong这个关键字改为weak,就修改为了弱引用的方式,当你声明一个弱变量时,系统会追踪赋值给这个变量的引用。当引用的对象释放时,弱变量会被自动设置为nil。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (strong,nonatomic) NSString *name;
//@property (strong,nonatomic) Person *person; //强引用
@property (weak,nonatomic) Person *person; //弱引用
@end
修改为弱引用之后,上面的函数打印的结果
2017-04-22 14:22:26.759 arcTest[13191:2983670] hu,1
2017-04-22 14:22:26.759 arcTest[13191:2983670] dong,1
2017-04-22 14:22:26.759 arcTest[13191:2983670] ========================
2017-04-22 14:22:26.759 arcTest[13191:2983670] hu,1
2017-04-22 14:22:26.759 arcTest[13191:2983670] dong,1
2017-04-22 14:22:26.759 arcTest[13191:2983670] ========================
2017-04-22 14:22:26.760 arcTest[13191:2983670] (null),(null)
2017-04-22 14:22:26.760 arcTest[13191:2983670] ========================
在bPerson设置为空时,aPerson.person中的name也变成了空值。
三、block块中的使用
在ViewController中,声明一个block
#import <UIKit/UIKit.h>
typedef void(^viewCallBack)(int);
@interface ViewController : UIViewController
@property(copy,nonatomic) viewCallBack callBack;
-(void)logCallback:(viewCallBack)callback;
@end
点击按钮的时候,跳转到SecondViewControler
-(void)initButton{
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 40)];
[btn setTitle:@"切换" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[self.view addSubview:btn];
[btn addTarget:self action:@selector(changeVC) forControlEvents:UIControlEventTouchUpInside];
}
-(void)changeVC{
SecondViewController *secondVC = [[SecondViewController alloc] init];
[self presentViewController:secondVC animated:true completion:nil];
}
-(void)logCallback:(viewCallBack)callback
{
self.callBack = callback;
NSLog(@"回调咯");
self.callBack(2);
}
在SecondViewController中,有一个变量m_person和name分别在.h和.m文件中;
SecondViewController.h文件
#import <UIKit/UIKit.h>
#import "Person.h"
@interface SecondViewController : UIViewController
@property (strong,nonatomic) Person *m_person;
@end
SecondViewController.m文件
#import "SecondViewController.h"
#import "ViewController.h"
@interface SecondViewController ()
{
NSString *name;
}
@end
@implementation SecondViewController
在SecondViewController中,点击按钮,返回界面并且调用block块,如果是不管强弱引用,直接调用
-(void)changeVC{
[((ViewController*)[self presentingViewController]) logCallback:^(int s) {
self.m_person.name = [NSString stringWithFormat:@"%d",s];
name = [NSString stringWithFormat:@"%d",s];
NSLog(@"%d",s);
}];
[self dismissViewControllerAnimated:YES completion:nil];
}
会发现虽然界面返回了,东西也打印了,看似没有问题,其实并没有调用SecondViewController的dealloc函数,也就是并没有销毁掉SecondViewController这个对象
-(void)dealloc{
NSLog(@"dealloc");
}
解决方案就是解决引用的问题,首先m_person需要使用弱引用,
__weak typeof(self) weakSelf = self;
weakSelf.m_person.name = [NSString stringWithFormat:@"%d",s];
而name因为并不是self的成员变量,默认是强引用,所以需要使用__strong来解决
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf->name = [NSString stringWithFormat:@"%d",s];
这样处理之后的代码就是这样
-(void)changeVC{
__weak typeof(self) weakSelf = self;
[((ViewController*)[self presentingViewController]) logCallback:^(int s) {
weakSelf.m_person.name = [NSString stringWithFormat:@"%d",s];
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf->name = [NSString stringWithFormat:@"%d",s];
NSLog(@"%d",s);
}];
[self dismissViewControllerAnimated:YES completion:nil];
}
这样处理之后,才会正确的调用dealloc的函数销毁对象。
四、Demo下载
Github下载地址:https://github.com/DamonHu/HudongBlogDemo/arcTest
Gitosc下载地址:http://git.oschina.net/DamonHoo/arcTest
五、参考文章
- 强引用和弱引用
- ARC如何获取retainCount
- IOS OC中NSString 对象的引用计数 打印输出不正确,求解答!
- OC中的block块的一点备忘
- 深入理解 weak-strong dance
- 对 Strong-Weak Dance 的思考
版权属于:东哥笔记 - DongGe.org
本文链接:https://dongge.org/blog/533.html
自2017年12月26日起,『转载以及大段采集进行后续编辑』须注明本文标题和链接!否则禁止所有转载和采集行为!