iOS之weak和strong、懒加载及循环引用
一、weak和strong
1.理解
刚开始学UI的时候,对于weak和strong的描述看得最多的就是“由ARC引入,weak相当于OC中的assign,但是weak用于修饰对象,但是他们都不会造成引用计数加1;而strong则相当于OC中规定retain,它会造成引用计数加1”。
ARC的原理:只要还有一个变量指向对象,对象就会保持在内存中。当指针指向新值,或者指针不再存在时,相关联的对象就会自动释放。这条规则对于实例变量、synthesize属性、局部变量都是适用的
strong指针能够保持对象的生命,一个对象只要有strong指针指向它,那么它就不会被释放;相反的,如果一个没有一个strong指针指向它,那么它将会被自动释放。默认所有实例变量和局部变量都是Stong指针
weak型的指针变量仍然可以指向一个对象,但不属于对象的拥有者。即当对象被销毁的时候,这个weak指针也就自动指向nil(空指针)。
MARK传送门:MJ对于weak和strong的解析
2.weak和strong指针使用注意
// 我们经常看到从xib中引用到控制器的属性都是weak型指针,为什么那些控件对象不会被自动释放?
@property(nonatomic,weak) IBOOutlet UIButton *btn;
// 原来在xib中创建或放置控件的时候,已经形成了这种引用关系
UIViewController->UIView->subView->UIButton
// 进入到UIViewcontroller.h文件中,发现
@property(null_resettable, nonatomic,strong) UIView *view; // 这货是强引用的
// 所以,上述的引用关系就是xib对这个button是强引用,你声明的属性对其是弱引用
@interface LZVC ()
@property (nonatomic,weak)UIView *myView; @end @implementation LZVC - (void)viewDidLoad { [super viewDidLoad];
//出现警告:("Warning: Assigning retained object to weak variable; object will be released after assignment")
_myView = [[UIView alloc] initWithFrame:self.view.frame];
_myView.backgroundColor = [UIColor redColor];
[self.view addSubview:_myView];
} @end // 我们会发现_myView根本就没有被添加到self.view上面,因为_myView是一个weak型指针,没有持有对象的能力,在其等号后面初始化的那个成员变量在刚刚被初始化之后便由于没有强指针引用它便被自动释放了,所以_myView得到的为空。
// 更正方法:
// ①将成员属性声明中的weak改为strong。(直接让_myView强引用初始化的对象,如此初始化的对象就不会被自动释放了)
// ②将出现警告的地方改为如下所示:
// 由于所有的实例变量和局部变量默认都是strong型指针,所以myView强引用初始化的对象,而后_myView弱引用myView
UIView *myView = [[UIView alloc] initWithFrame:self.view.frame]; UIView *myView.backgroundColor = [UIColor redColor]; _myView = myView; [self.view addSubview:_myView];
3.weak和strong的使用时机(根据上面的特征,我做出如下测试)
1> 我新建了一个继承自UIView的子类TestView,新增了一个属性text,重写了它的dealloc方法,我想看看TestView什么时候释放
@property (nonatomic,copy)NSString *text; // 属性
// 重写Dealloc并打印数据 -(void)dealloc { NSLog(@"%@----%s",self.text,__func__);
[super dealloc]; }
2> 在控制器中,我写了如下代码
#import "LZVC.h" #import "TestView.h" @interface LZVC () @property (nonatomic,weak)TestView *myWeakView; //弱引用 @property (nonatomic,strong)TestView *myStongView; //强引用 @end @implementation LZVC - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; TestView *myWeakView = [[TestView alloc] initWithFrame:CGRectMake(0, 64, 160, 160)]; myWeakView.backgroundColor = [UIColor redColor]; myWeakView.text = @"我是弱引用的"; _myWeakView = myWeakView; [self.view addSubview:_myWeakView]; TestView *myStrongView = [[TestView alloc] initWithFrame:CGRectMake(160, 64, 160, 160)]; myStrongView.backgroundColor = [UIColor greenColor]; myStrongView.text = @"我是强引用的"; _myStongView = myStrongView; [self.view addSubview:_myStongView]; } #pragma mark点击屏幕触发 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { if (self.myWeakView) { [self.myWeakView removeFromSuperview]; } if (self.myStongView) { [self.myStongView removeFromSuperview]; }
}
3> 点击屏幕后,两个view都从屏幕上被移除了,有如下打印,我们发现,弱引用的TestView被释放了(通过addSubviews,myWeakView有控制器对其强引用)
4> 我返回主页,让这个LZVC控制器被销毁,又有打印,强引用的TestView(myStrongView除了控制器对其强引用外,声明的属性也对其强引用)
5> 总结:相信从3、4的打印中都明白了,如果你想让一个控件的生命周期随着你的控制器被销毁才去释放,那就使用strong;如果你仅仅是想让它在被移除之后就被销毁,那就使用weak
二、懒加载
1.懒加载
懒加载——也称为延迟加载,即在需要的时候才加载(效率低,占用内存小)。所谓懒加载,其实就是重写getter方法。说的通俗一点,就是在开发中,当程序中需要利用的资源时。在程序启动的时候不加载资源,只有在运行当需要一些资源时,再去加载这些资源。
我们知道iOS设备的内存有限,如果在程序在启动后就一次性加载将来会用到的所有资源,那么就有可能会耗尽iOS设备的内存。这些资源例如大量数据,图片,音频等等,所以我们在使用懒加载的时候一定要注意先判断是否已经有了,如果没有那么再去进行实例化。
2.使用懒加载的好处
1> 不必将创建对象的代码全部写在viewDidLoad方法中,代码的可读性更强
2> 每个控件的getter方法中分别负责各自的实例化处理,代码彼此之间的独立性强,松耦合。且其中还进行了非空判断,防止对象被重复加载
3> 只有当真正需要资源时,再去加载,节省了内存资源,防止对象被提前创建,也防止了使用对象时对象还没被创建的问题(内存优化,如加载plist文件等耗内存的操作)。
3.使用懒加载初始化成员变量
@interface LZVC () @property (nonatomic,strong)NSArray *dataSource; @end @implementation LZVC #pragma mark 懒加载 -(NSArray *)dataSource { if (_dataSource == nil) { _dataSource = @[@"1",@"2",@"3",@"4"]; } return _dataSource; } // 最后在用的时候采用self.dataSource形式方式即可
这里顺便说一说成员变量和属性的问题:
1> 直接访问成员变量:_dataSource = @[@"5",@"6"];
直接赋值,直观,快捷。
2> 访问成员属性:self.dataSource = @[@"5",@"6"];
当进行赋值的时候会走setter方法,当获取值的时候会走getter方法,我们可以在这两个方法里面做点自己想做的事情(例:在setter方法里面控制下数据有效性、监听值的改变等;而getter方法里面懒加载就可以体现出其好处了。
三、循环引用问题(场景)
1.经典:代理模式Delegate(UITableViewDelegate)举例
控制器的view强引用Tableview,而tableview的delegate又是控制器,如果下面两个代理属性用strong去修饰,就会造成循环引用问题,解决这个问题的最好办法就是两者其中之一对其弱引用就可以了(weak)。
@property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource; @property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
2.NSTimer定时器Target造成循环引用,NSTimer会持有target对象。
3.block作为成员变量,而在block中又访问了self或其属性造成循环引用