基础知识
iOS提供了两种管理内存的方式,分别为MRC(手动)和ARC(自动)。
MRC(人工引用计数),手动管理内存。
MRC模式下,所有的对象都需要手动的添加retain、release代码来管理内存。使用MRC,需要遵守谁创建,谁回收的原则。也就是谁alloc,谁release;谁retain,谁release。
当引用计数为0的时候,必须回收,引用计数不为0,不能回收,如果引用计数为0,但是没有回收,会造成内存泄露。如果引用计数为0,继续释放,会造成野指针。为了避免出现野指针,我们在释放的时候,会先让指针=nil。
ARC(自动引用计数),自动管理内存。
在ARC模式下,只要没有强指针(强引用)指向对象,对象就会被释放。在ARC模式下,不允许使用retain、release、retainCount等方法。并且,如果使用dealloc方法时,不允许调用[super dealloc]方法。
ARC模式下的property变量修饰词为strong、weak,相当于MRC模式下的retain、assign。strong :代替retain,缺省关键词,代表强引用。weak:代替assign,声明了一个可以自动设置nil的弱引用,但是比assign多一个功能,指针指向的地址被释放之后,指针本身也会自动被释放。
@property修饰的内存管理
MRC : retain、assign、copy
ARC : strong、weak、copy
关于栈和堆
栈(stack): 由编译器自动分配释放,存放函数的参数值,局部变量的值等
堆(heap): 一般由程序员分配释放,若程序员不释放,程序结束时可能由系统回收
OC对象存放于堆里面(堆内存要程序员手动回收)
非OC对象一般放在栈里面(栈内存会被系统自动回收)
我们创建一个oc对象,定义一个变量,调用一个函数或者方法都会提高App的内存占用。
内存泄露和过度释放
iOS开发过程中,使用Objective-C分配的堆内存都是通过引用计数来做保留和释放的。一块内存初始分配,引用计数为1,此后每新增一个强引用,引用计数增加1;释放正好相反,每一次release,引用计数减1,直到为0,对象所用内存被真正free掉,以被再次复用。然而,实际开发当中,总有一些原因导致引用计数无法按正常逻辑减少到0,或者减少到0之后仍然被调用release,前者是内存泄露,后者则是过度释放。
当内存泄露发生时,运行的App不会直接第发生明显问题,但废弃内存得不到回收,在长时间持续运行后,App进程会由于可用内存不断变低而被kill或带来其它隐患。(内存泄露的隐患)
过度释放,是对同一个对象释放了过多的次数,其实当引用计数降到0时,对象占用的内存已经被释放掉,此时指向原对象的指针就成了“悬垂指针”,如若再对其进行任何方法的调用,(原则上)都会直接crash(然而由于某些特殊的情况,不会马上crash)。(过度释放的隐患)
内存工具(Instruments)
内存泄漏
为了更好地处理内存泄露的问题,平时在写代码的时候要注意循环引用,学会善用weak,良好的编程习惯,检查一些显而易见的语法问题。
对于运行时出现的问题,我们可用 Instruments 中的 Allocation 和 Leaks 来不断重复操作App,发现和定位内存泄露点。当运行时发生显示内存泄露时, Leaks 会在时间轴上标出红色指示线,同时在 Instruments 的下方会列出调用细节,结合系统提供的 malloc 历史,其中包含引用计数变化情况,以及调用栈可以很直接地找到泄露原因。
同时对于一些“隐式”的情况,需要反复操作,同时观察 Allocation 中只增不减,一直创建新对象而不释放老对象的情况。
野指针与僵尸对象
野指针:指针指向的对象已经被回收掉了,这个指针就叫做野指针
僵尸对象:一个已经被释放的对象,就叫做僵尸对象
详情点这里
我们有两种方式找到产生的原因:
1.使用 Instruments 的 Zombies 工具 详情点这里
2.手动开启 Product->Scheme->Edit Scheme->Run xxx.app,在右边框中选中 Enable Zombie Objects(手动开启僵尸对象,会导致内存增长,也会影响 Leaks 工具的调试)