17 - 内存管理方案之MRC和ARC
在上文我们知道了内存的五大区域组成,接下来了解对于内存的管理。
内存管理其实就是管理堆
因为分配堆的空间大小不确定,而且它的生命周期不确定,需要人为管理。
栈的数据虽然也是运行时临时分配的,但是其会在方法调用结束后自动回收,所以无需关注。
全局区/静态区和常量区、代码区的生命周期都是APP运行的整个过程,所以系统会在APP运行结束后自动回收。
因此我们常说的内存管理很大程度上就是指管理堆
引用计数的认识
iOS提供引用计数方式来管理内存,当计数器为0时,就表示这个对象就成为了垃圾对象,系统会在合适的时候进行回收。
引用计数的时机:
- 新创建一个引用类型对象(alloc/new/copy/mutableCopy),计数为1
- retain的+1操作(只要被别人用到了,就要+1)
- 将对象赋值给其他变量
- 将对象赋值给其他实例变量或属性
- 将对象加入到集合中
- 将对象作为参数传递
- 将对象作为返回值
- release的-1操作
- 将变量赋值为nil或其他值
- 将属性或实例变量设置为nil或其他值
- 实例变量或属性所在的对象被释放
- 参数或局部变量离开函数时
- 将对象从集合中删除
文艺的解释
记得在《寻梦环游记》里对于一个人的死亡是这样定义的:当这个这个世界上最后一个人都忘记你时,就迎来了终极死亡。类比于引用计数,就是每有一个人记得你时你的引用计数加1,每有一个人忘记你时,你的引用计数减1,当所有人都忘记你时,你就消失了,也就是从内存中释放了。
如果再深一层,包含我们后面要介绍的ARC中的强引用和弱引用的话,那这个记住的含义就不一样了。强引用就是你挚爱的亲人,朋友等对你比较重要的人记得你,你的引用计数才加1。
而弱引用就是那种路人,一面之缘的人,他们只是对你有一个印象,他们记得你是没有用的,你的引用计数不会加1。当你挚爱的人都忘记你时,你的引用计数归零,你就从这个世界上消失了,而这些路人只是感觉到自己记忆中忽然少了些什么而已。
MRC手动引用计数
早期需要手动管理,也就是手动对引用计数进行加减,现在都是在系统自动进行引用计数的管理。
注意:
- 原则:计数加一和减一的调用次数一定要匹配,
- 计数为0时,系统自动调用dealloc方法来回收
- 内存回收后,只是代表着这个空间可以分配给别人,而不是真的空间被删掉,或者内存数据被删掉
- 在MRC模式下,必须遵守:谁创建,谁释放,谁引用,谁管理
ARC自动引用计数
ARC简单来说就是系统帮我们自动加上release(autoRelease)和retain方法,不需要手动添加。
注意:当对象没有强指针引用时,会被回收,注意这里一定要是强指针,因为有弱指针,也会被立即回收
所有权修饰符
使用ARC时,处理对象类型或id类型时,一定要加上所有权修饰符,没有加的话就默认是__strong,即强引用
- __strong:默认所有权修饰符,表示强引用,会增加引用计数
- __weak:表示弱引用,不会增加引用计数,只会在对象的弱引用计数表中进行计数。
- 可用于解决循环引用问题。
- 当对象被释放前会通过判断是否存在弱引用表,所有指向它的弱引用都会被释放掉,这样可以避免野指针问题。
- 不可以修饰基本数据类型,只能修饰引用类型
- iOS5以上版本可使用
- _unsafe_unretained:与__weak类似,区别在于,不能在对象被释放后自动将引用设置为nil,iOS5以下版本使用,所以无需关注
- __autoreleasing:
- 它可以将对象注册到autorelease,也就等同于在MRC下调用对象的autorelease方法
- 在ARC中,可以在@autorelease{}中来使用
- 一般来说会自动添加,不用显式使用__autorelease
属性修饰符
分别查看不同的属性修饰符它们所代表的所有权修饰符是什么
- assign:__unsafe_unretained
- 因此他不能在对象被释放后自动将引用设置为nil,所以以后只能用在基本数据类型,而不能用在引用类型
- retain:__strong
- retain也会增加引用计数
- strong:__strong
- 可以用在ARC上对属性进行修饰,作为强引用
- copy:__strong
- copy的所有权也是__strong,所以也会进行强引用
- 区别在于会重新开辟一个空间,指向该引用,新对象引用+1,原来的对象并不会引用+1
- weak:__weak
- 在ARC中使用,作为弱引用。
- __unsafe_unretained:__unsafe_unretained
- 不要用,仅了解,因为只在iOS5以下版本才会使用
默认的关键字为:
引用类型:@property (atomic,readWrite,strong) UIView *view;
基本数据类型:@property (atomic,readWrite,assign) int *aa;
野指针和僵尸对象
野指针
指向一个已经被删除的对象或者访问受限内存区域的指针就是野指针
注意:野指针不是nil指针,而是指向了垃圾内存的指针
野指针的场景:
- 对象释放后,指针没有置空
- 使用unsafe_unretained修饰符,对象释放后,没有手动置为nil
- KVO没有移除观察者
- 对象提前释放
- 异步函数中block使用的self没有强引用,导致外部已经释放掉,但是里面还在使用
- 对象多次释放
- 多个线程同时对某个对象赋值但没有加锁,就可能多次release。
僵尸对象
一个已经被释放掉的对象就是僵尸对象
一个OC对象的引用计数为0,调动dealloc后释放之后,就是僵尸对象。
一个对象虽然被释放掉了,但是数据依然在内存中,所以如果通过野指针去访问僵尸对象,一旦这个僵尸对象的内存已经被分配给其他人了,就会出错。
为什么不开启僵尸对象检测?
这样每次通过指针访问对象的时候都会检查是否为僵尸对象,这样很影响效率