一 一个对象的内存管理
1. 管理范围:任何继承了 NSObject 的对象.
2. 对象的基本结构:每个 OC 对象都有自己的引用计数器,是一个整数,表示”对象被引用的次数”,(一个引用计数器占4个字节),当对象计数器减为0的时候,对象会被回收.
3. 每个 OC 对象内部专门有4个字节的存储空间来存储引用计数器.
4. 引用计数器的作用:
1⃣ 当使用 alloc,new 或 copy 创建一个新对象的时候,新对象的引用计数器默认为1.
2⃣ 当一个对象的引用计数器值为0的时候,对象占用的内存就会被系统回收.换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占的内存就不可能被回收,除非整个程序已经退出.
5. 计数器的操作
1⃣ 给对象发送一条 retain 消息,可以使引用计数器+1(retain 方法返回对象本身)
2⃣ 给对象发送一条 release 消息,可以使引用计数器值-1
3⃣ 可以给对象发送 retainCount 消息获得当前引用计数器值
6. 对象的销毁
1⃣ 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
2⃣ 当一个对象被销毁时,系统会自动向对象发送一条 dealloc 消息
3⃣ 一般会重写 dealloc 方法,在这里释放相关资源, dealloc 就像对象的遗言
4⃣ 一旦重写了 dealloc 方法,就必须调用[super dealloc],并且放在最后面调用
5⃣ 不要直接调用 dealloc 方法
6⃣ 一旦对象被回收了,它占用的内存就不在可用,坚持使用会导致程序崩溃(野指针错误)
7⃣ 指向僵尸对象的指针(不可用内存),所占用的内存已经被回收的对象,僵尸对象不能再使用
8⃣经典错误 EXC_BAD_ACCESS:访问了一块坏得内存(已经被回收,已经不可用的内存),即野指针错误.解决方案,将指针清空(p = nil)
9⃣ OC中不存在空指针错误,给空指针发送消息,不报错.
指向僵尸对象的指针,叫做野指针.给野指针发送消息会报错.
没有指向任何东西的指针(存储东西是 nil,NULL,0),给空指针发送消息不会报错.
二 对多对象的内存管理
1.想使用某个对象,就应该让对象的计数器+1 (让对象做一次 retain 操作)
2.你不想在使用某个对象,就应该让对象的计数器-1(让对象做一次 release)
3.谁 retain, 谁 release
4.谁 alloc,谁 release.
5.内存管理代码规范:
1⃣ 只要调用了 alloc 必须有 release(autorelease)
2⃣ set 方法的代码规范
基本数据类型:直接复制
- (void)setAge:(int)age
{
_age = age;
}
OC 对象类型:
- (void)setCar: (Car *)car{
//1.先判断是不是新传进来的对象
if(car != _car){
2.对旧对象做一次release
[_car release];
3.对新对象做一次retain
_car = [carretain];
}
}
3⃣ dealloc 的代码规范
一定要[super dealloc],而且放到最后面;
对 self 所拥有的其他对象做一次 release
- (void)dealloc{ [_car release]; [super dealloc];}
6.对象如果不是通过 alloc 产生的,就不需要 release
三
1. 基本原理
1⃣ 由于移动设备的内存极其有限,所以每个 APP 所占的内存也是限制的,当 app 所占的内存较多时,系统就会发出警告,这时需要回收一些不需要再继续使用的内存空间,比如回收一些不在使用的对象和变量等.
2⃣ 管理范围:任何继承 NSObject 的对象,对其他的基本数据类型无效.
3⃣ 本质原因是因为对像和其他数据类型在系统中的存储空间不一样,其他局部变量主要存放在栈中,而对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露.
2. 对象的基本结构
每个 OC 对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象,对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁.
在每个 OC 对象内部,都专门有4个字节的存储空间来存储引用计数器.
3. 引用计数器的作用
判断对象要不要回收的唯一依据就是计数器是否为0,若不为0则存在.
4. 操作
给对象发送消息,进行相应的计数器操作.
Retain 消息:使计数器+1,该方法返回对象本身
Release 消息:使计数器-1(并不代表释放对象)
retainCount 消息:获得对象当前的引用计数器值
5. 对象的销毁
1⃣ 当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收.
2⃣ 当对象被销毁时,系统会自动向对象发送一条 dealloc 消息,一般会重写 dealloc 方法,在这里释放相关的资源, dealloc 就像是对象的”临终遗言”.一旦重写了 dealloc 方法就必须调用[super dealloc]; 并且放在代码的最后调用(不能直接调用 dealloc 方法)
一旦对象被回收了,那么它所占的内存空间就不在可用,坚持使用会导致程序崩溃(野指针错误).
6. 相关概念和使用注意
野指针错误:访问了一块坏得内存(已经被回收,不可用的内存),指向僵尸对象的指针.
僵尸对象:所占的内存已经被回收的对象,僵尸对象不能被使用.
空指针:就是没有指向任何东西的指针(nil,0,NULL),给空指针发送消息,是不会报错的.
注意:不能使用[p retain]让僵尸对象起死回生
7. 通过引用技术管理内存的方式有两种方式: MRC 和 ARC
1⃣ MRC:(Manual Reference Count),手动引用计数器,由开发人员通过引用计数去管理内存
2⃣ ARC:(Automatic ReferenceCount),自动引用计数,由系统自动通过引用计数管理内存
ARC是基于 MRC创建出来的;
8. 使用 alloc 生成对象的时候,会将引用计数由0变为1.
打印引用计数,使用 retainCount; 打印 retainCount 的时候,一定要在 MRC环境下.
将[person release]; person = nil, 将对象置为空指针()
9. autoreleasepool 自动释放池,autorelease 必须下载自动释放池中.
Autorelease的实质是:对一个对对象使用 autorelease 操作,这个对象的引用的引用计数不会立即减1,对象会被放到自动释放池里面,等这个池子结束时,会对对象的引用计数一次减1.
系统会将 autoreleasepool 中的对象保存起来,(以栈的方式,把对象压入栈),在 autorelepool 运行结束的时候,会向保存的对象逐一发送 release 消息,
10. 创建一个数组,将创建的对象添加到数组中,在向数组添加元素的时候,(数组会对添加的对象做引用计数器加1的操作),如果使用便利构造器创建的类型,不需要进行手动释放,系统会自动释放;如果使用 alloc 方法创建的类型,在结束时候需要手动的释放创建的对象.
11. Copy的使用
1⃣ 对像使用 copy 的前提是,这个类遵循 NSCopying 协议,而且必须实现协议内的方法
2⃣ copy 方法的实现
在类的. M文件中,实现协议必须要实现的方法:
深拷贝:返回一个新的对象,跟原来的对象所占空间大小相同,而且空间的内容也相同
- (void)copyWithZone:(NSZone *)zone{
Person *p = [[Person allocWithZone:zone]init];
P.age = self.age;
P,name = self.name;
Return p;
}
浅拷贝:拷贝的是地址.
12. New: 申请内存将 retainCount 从0变为1.
13. 总结内存管理的原则:
1⃣ 加1操作的(alloc,new,retain,copy)
2⃣ 减1操作的:(release,autorelease)
3⃣ 一旦对象的引用计数器为0;系统会自动调用 dealloc 方法,之后就不能再对对象进行操作了
4⃣ 谁 alloc, 谁 release, 自己创建的对象,自己管理
九 内存管理的原则
1. 有人在使用对象,对象就不能被回收;不想使用对象的时候,让对象的引用计数器减1.
2. 谁创建,谁释放(如果通过 alloc,new,copy 创建一个对象,必须调用 release 或者 autorelease 方法进行释放)
3. 谁 retain, 谁 release(只要你调用了 retain, 就需要使用 retain 进行释放)
4. autorelease 的使用注意事项:
1⃣ @autoreleasepool 可以随意创建,也可以嵌套使用
2⃣ 如果把一个对象重复加到自动释放池,会出错([p1 autorelease], [p1 autorelease]),第一次释放正确,第二次释放错误.
3⃣ 操作内存比较大得元素的时候,不要随便发的使用,操作内存比较小的对象的时候可以使用
十 内存管理的高级
1. retain中 setter 方法最严谨的写法(release 旧值, retain 新值)(适用于 OC 对象类型)
if(_name != name){
[_name release];
_name = [name retain];
}
判断传入的值与原来的值是否相等,如果不相等,对旧址做一次 release操作,对新值做一次 retain 操作,然后在进行赋值.
在 setter 方法进行赋值的时候,需要进行判断:
1⃣ ([b1 release]; [p1 setBook:b1])这个时候再操作就会因为操作僵尸对象引起Crach
2⃣ 避免重复赋值相同数据.
1. Copy 中 setter 方法最严谨的方法(release 旧值, copy 新值)
if(_name != name){
[_name release];
_name = [name copy];
}
2. Assign 主要用于基本数据类型和遵循了协议的实例变量,使用内存组 assign 进行修饰
3. 当完全重写一个setter 和 getter 方法的时候,一定要把属性的实现部分写上@ synthesize name = _name;
4. 便利构造器的内存管理
1⃣ 方式1:Person *per = [[Person alloc]initWithName:name age:age];
[p release]; error对象已经释放,没有办法返回
return [p autorelease]; 是管理便利构造器的完美解决方法,既不会造成内存泄露,也不会产生野指针.
2⃣ 方式二:
return [[[Person alloc]initWithName:name age:age]autorelease];
5. 便利构造器的内存管理:
a. 便利构造器方法内部使用 autorelease 释放对象
b. 通过便利构造器生成的对象不用释放
c. 在便利构造器中,内存自动管理,谁创建,谁管理
d. 不能在创建的便利构造器中添加 release 操作
6. 内存管理总结:
a. 当你占用对象的时候,我们要让引用计数器加1,做一次 retain 操作
b. 当你不想占用某个对象的时候,要让它的引用计数器减1,(也就是做一次 release)
c. 谁 alloc, 谁 release, 便利构造器要用 autorelease
d. cmd + shift + b 用来检测是否有内存泄露
7. dealloc 是 NSObject 的一个实例方法,与alloc 对应,用于回收开辟的内存空间;这个方法在引用计数为0的时候,由系统自动调用,通常我们在 dealloc中释放类的实例变量.
注意事项:永远不要手动调用 dealloc; 在 dealloc 方法的最后一行,必须要写[super dealloc];