1. 什么是内存管理
- 程序在运行的过程中通常通过以下行为,来增加程序的的内存占用
- 创建一个OC对象
- 定义一个变量
- 调用一个函数或者方法
- 而一个移动设备的内存是有限的,每个软件所能占用的内存也是有限的
- 当程序所占用的内存较多时,系统就会发出内存警告,这时就得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等
- 如果程序占用内存过大,系统可能会强制关闭程序,造成程序崩溃、闪退现象,影响用户体验
所以,我们需要对内存进行合理的分配内存、清除内存,回收那些不需要再使用的对象。从而保证程序的稳定性。
那么,那些对象才需要我们进行内存管理呢?
- 任何继承了NSObject的对象需要进行内存管理
- 而其他非对象类型(int、char、float、double、struct、enum等) 不需要进行内存管理
这是因为
- 继承了NSObject的对象的存储在操作系统的
堆
里边。 - 操作系统的
堆
:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表 - 非OC对象一般放在操作系统的
栈
里面 - 操作系统的
栈
:由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈(先进后出) - 示例:
int main(int argc, const char * argv[]) { @autoreleasepool { int a = 10; // 栈 int b = 20; // 栈 // p : 栈 // Person对象(计数器==1) : 堆 Person *p = [[Person alloc] init]; } // 经过上面代码后, 栈里面的变量a、b、p 都会被回收 // 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1 return 0; }
2. 内存管理模型
提供给Objective-C程序员的基本内存管理模型有以下3种:
- 自动垃圾收集(iOS运行环境不支持)
- 手工引用计数和自动释放池(MRC)
- 自动引用计数(ARC)
3.MRC 手动管理内存(Manual Reference Counting)
1. 引用计数器
系统是根据对象的引用计数器来判断什么时候需要回收一个对象所占用的内存
- 引用计数器是一个整数
- 从字面上, 可以理解为”对象被引用的次数”
- 也可以理解为: 它表示有多少人正在用这个对象
- 每个OC对象都有自己的引用计数器
- 任何一个对象,刚创建的时候,初始的引用计数为1
- 当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1
- 当没有任何人使用这个对象时,系统才会回收这个对象, 也就是说
- 当对象的引用计数器为0时,对象占用的内存就会被系统回收
- 如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出 )
2. 引用计数器操作
- 为保证对象的存在,每当创建引用到对象需要给对象发送一条retain消息,可以使引用计数器值+1 ( retain 方法返回对象本身)
- 当不再需要对象时,通过给对象发送一条release消息,可以使引用计数器值-1
- 给对象发送retainCount消息,可以获得当前的引用计数器值
- 当对象的引用计数为0时,系统就知道这个对象不再需要使用了,所以可以释放它的内存,通过给对象发送dealloc消息发起这个过程。
- 需要注意的是:release并不代表销毁\回收对象,仅仅是计数器-1
int main(int argc, const char * argv[]) { @autoreleasepool { // 只要创建一个对象默认引用计数器的值就是1 Person *p = [[Person alloc] init]; NSLog(@"retainCount = %lu", [p retainCount]); // 1 // 只要给对象发送一个retain消息, 对象的引用计数器就会+1 [p retain]; NSLog(@"retainCount = %lu", [p retainCount]); // 2 // 通过指针变量p,给p指向的对象发送一条release消息 // 只要对象接收到release消息, 引用计数器就会-1 // 只要一个对象的引用计数器为0, 系统就会释放对象 [p release]; // 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1 NSLog(@"retainCount = %lu", [p retainCount]); // 1 [p release]; // 0 NSLog(@"--------"); } // [p setAge:20]; // 此时对象已经被释放 return 0; }
3. dealloc方法
- 当一个对象的引用计数器值为0时,这个对象即将被销毁,其占用的内存被系统回收
- 对象即将被销毁时系统会自动给对象发送一条dealloc消息(因此,从dealloc方法有没有被调用,就可以判断出对象是否被销毁)
- dealloc方法的重写
- 一般会重写dealloc方法,在这里释放相关资源,dealloc就是对象的遗言
- 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
- (void)dealloc { NSLog(@"Person dealloc"); // 注意:super dealloc一定要写到所有代码的最后 // 一定要写在dealloc方法的最后面 [super dealloc]; }
- 使用注意
- 不能直接调用dealloc方法
- 一旦对象被回收了, 它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
4. 野指针和空指针
- 只要一个对象被释放了,我们就称这个对象为 "僵尸对象(不能再使用的对象)"
- 当一个指针指向一个僵尸对象(不可用内存),我们就称这个指针为野指针
- 只要给一个野指针发送消息就会报错(EXC_BAD_ACCESS错误)
int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init]; // 执行完引用计数为1 [p release]; // 执行完引用计数为0,实例对象被释放 [p release]; // 此时,p就变成了野指针,再给野指针p发送消息就会报错 [p release]; } return 0; }
- 为了避免给野指针发送消息会报错,一般情况下,当一个对象被释放后我们会将这个对象的指针设置为空指针
- 空指针
- 没有指向存储空间的指针(里面存的是nil, 也就是0)
- 给空指针发消息是没有任何反应的
int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init]; // 执行完引用计数为1 [p release]; // 执行完引用计数为0,实例对象被释