内存管理小结
一、基础知识
1. property
@property
作用是声明属性,也就是快速方便为实例变量创建存取器(accessor),并允许我们用点语法使用存取器,它代表一个表现对象的指针。assign,retain,strong,weak,还有copy,这些都是一个property在声明中可以指定的属性,这些属性的不同之处就在于这个指针。
Objective-C中的@property
存取器(accessor) 用于获取和赋值实例变量的方法,即get 和 set 方法。
2. self. 和下划线的区别
- self. 是调用属性(对象),会调用 get 和 set 方法, _xx只是调用变量
例如对一个数值实现了懒加载, 那在之后的调用中必须用 self. ,而不可以用下划线 - self.调用系统的 get,set 方法的步骤: 如果用户自定义了 get 和 set 方法, 则不会再生成. 如果用户没有自定义, 系统才会自动生成。(在 ios5之前, 属性声明需要@synthesize指令告诉编译器帮助生成 get 和 set 方法, ios5之后系统会自动生成 )
- self. 会使引用计数器+1, 而_xx 不会
- self. 可以获取到父类的属性
- _xx 忽略了 self 指针, 容易在 block 中造成循环引用(weakSelf)
最后总结:self方法实际上是用了get和set方法间接调用,下划线方法是直接对变量操作。
//以前马虎犯的错误, 导致自动计算行高一直错误, 界面混乱
- (void)setBaskModel:(BaskModel *)baskModel{
self.baskModel = baskModel;❌
_baskModel = baskModel;
}
3.引用计数器
- 为什么引入引用计数器的概念?
如果没有引用计数器, 默认在一个代码块中执行完毕就会释放对象, 那在其他方法中再想调用此对象就调用不到了. 所以引入引用计数器的概念。 - 规则: 当引用计数器为0时, 对象会被释放。
alloc init方法,默认计数器值(retainCount)为1;
retain方法, 计数器值+1;
release 方法, 计数器值-1;
alloc:为一个新对象分配内存,并且它的引用计数为1。调用alloc方法,你便有对新对象的所有权
copy:制造一个对象的副本,该副本的 引用计数为1,调用者具有对副本的所有权
retain:使对象的引用计数加1,并且获得对象的所有权 - ARC 是如何引用引用计数器的?
ARC的内存管理,是编译器在编译时自动添加retain,release,autorelease
二、 strong&weak、copy和assign 的比较
1. strong
用于 oc 对象类型(NSArray、NSDate、NSNumber、模型类)
- 对象被一个强指针指向,一个对象只要有强指针引用着,就不会被销毁,这就是我们经常说的强引用。
- 强指针的生成:将指向对象的指针重新拷贝一遍,原对象的内存地址没有发生变化,引用计数+1,通俗点的理解就是指针拷贝(引用场景:类似于一条被牵着的狗,你用strong修饰一下,就相当于给这只狗又加了一条绳子,但是狗还是那条狗)。
2.weak
用于 UI控件,delegate
- 对象被__weak类型的指针指向,如果所有的strong类型的指针都消失以后,对象就会被释放掉,__weak指针会自动置为nil,防止野指针。(指针实际上也是一个变量,占有一定的存储空间,只不过它存储的内容是其他变量的内存地址)
- __weak指向对象时,对象的引用计数不变,即property不持有这个对象。
对比
strong类型的指针就是像是拴住的狗,只要你用绳子拴住狗,那么狗就不会跑掉。如果有5个人都牵着这一条狗(5条绳子牵一只狗),类比为5个strong类型指针指向一个对象。除非5个绳子都脱落,否则狗是不会跑掉的,类比,5个strong指针都=nil,则该对象释放。
weak型指针就像是一个小孩子指着狗喊道:“看,有一只狗在那里”,只要狗一直被拴着,那么小孩子就能看到狗 (weak指针)会一直指向它,只要狗的绳子脱落,那么狗就会跑掉,不管有多少的小孩在看着它。
3.assign
用于非OC对象类型,‘基本数据类型’、‘枚举’、‘结构体’ 等,这些数值主要存在于栈上,系统自动管理。
- 引用计数器不变,但是和 weak 不同的是当对象销毁时,指针不会自动置nil,这时的property可能就会成为一个悬垂指针,我们访问它指向的内存地址时,可能就会出现一些意想不到的状况。
4.copy
用在修饰有可变对应类型的不可变对象上,如NSString, NSArray, NSDictionary。
NSMutableString 用 strong, 因为 mutableString 之后还要进行改变
- 对象会被重新拷贝一遍(即会重新分配一个内存地址),并给这个新建的对象引用计数设置为1,通俗点的理解就是对象拷贝(引用场景:类似于重新创造了一只狗,并给这只狗套上了一条绳)。
//如果用 strong的话,看一下
NSString *text1 = @“oldText”;
NSString *text2 = text1;
text2 = @“newText”; //用 strong 的话 text1 也是@“newText”
小结

- weak设计的初衷就是为了避免循环引用的问题,最典型的循环引用场景就是 delegate 和 IBOutlet。
weak 修饰的变量指向的内存如果被释放, 则会自动指向 nil, 因为响应 delegate 的vc 可能已经被释放了, delegate 指向的内存空间就会释放, 用 weak 才不会导致野指针错误.

从sb中拖线出来的UI控件必然会被其在SB中的父控件以addSubview方法或其他方法强引用,[self.view addSubview: label];
如果我们在控制器或者其父控件中再定义一个strong的属性来指向它的话File's owner
,该UI控件的引用计数器会被两次+1,如此这般当其控制器或者父控件被回收的时候,该UI控件无法销毁,造成内存泄露。

如果我们创建了一个对象,但是却没有另一个对象对他进行强引用,那么我们所创建的那个对象,会在创建完成后立即被销毁,所以代码声明的subview 一般用strong。

-
copy 防止多个对象引用一个对象时,赋值时相互影响的情况
copy.png
总的来说,OC的内存管理机制就是要保证引用计数平衡,当对象释放时使引用计数为0,大于零则会造成内存泄漏。