iOS 的内存管理分为 MRC 跟 ARC
一 、 为什么要管理内存
当
app
所占的内存比较多的时候,系统会发出内存警告
这时得回收一些不需要再使用的内存空间。
比如一些不需要使用的对象、变量等、
内存管理的范围:
任何继承了NSObject的对象
对其他数据类型如:
int
char
float
double
struct
enum
等 不需要管理内存内存的结构
🍎 内存分为五大区域
其中的两大区域
🍇 栈区
🍇 堆区
🍇 栈区
存放一些局部变量
例如:int a = 10;
int a = 10;
int b = 20;
Person *p; // Person类型的指针p
🍇 堆区
存放动态产生的数据 (alloc) 出来的数据
例如:Person对象
[Person alloc];
在OC中 此区域的对象在没有变量指针指向它的时候
它也不会自动销毁或者是自动释放
需要使用本对象调用回收机制的方法来释放这个对象
在Java中不一样,当没有指针指向对象的时候,垃圾回收机制会自动回收这种对象。
🍒 数据区
🍇 存放一些静态变量和字符串常量
🍒 代码区
🍇 存放代码
二 、引用计数器的基本概念
🍎 对象的基本结构
每个对象内部都会特意分配4个字节
的空间,存放一个整型的引用计数器
用来计算 “对象被引用的次数”
也就是说有多少个指针在使用这个对象。
🍎 引用计数器的作用
🍇 当使用alloc
new
copy
创建一个对象的时候,这个对象内部的引用计数器的值默认就是1
🍇 当一个对象的引用计数器的值为0
的时候
它所占用的内存就会被系统回收掉
换句话说
如果对象计数器不为 0 的时候
那么在整个程序的运行过程中 所占的内存
就不肯能被回收,除非整个程序已经退出。
三 、 引用计数器的用法
- 方法的基本使用
1> retain :计数器+1,会返回对象本身
2> release :计数器-1,没有返回值
3> retainCount :获取当前的计数器的值
4> dealloc
* 当一个对象要被回收的时候,就会调用 dealloc
* 但要调用[super dealloc],这句调用要放在最后面
* ARC中虽然可以重写 dealloc 方法、但不用调 [super dealloc] 而且也不可以调用
/
- 概念
1> 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用
2> 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)
3> 空指针 :没有指向任何东西的指针(指针存储的东西是nil、NULL、0) OC 不存在空指针错误,给空指针发送消息不会报错
[object retain]; // 使引用计数器的值 +1 (调用 retain 方法会返回对象的本身)
[object release]; // 引用计数器的值 -1 (调用 release 无返回值)
[object retainCount]; // 给对象发送一条 retainCount 消息 可以获取对象当前计数器的值
野指针错误 / 经典错误
在OC中指向僵尸对象 或指向(不可用内存)的指针 称为野指针
在OC中给野指针发送消息会报错 EXC_BAD_ACCESS 访问了一块坏的内存(已经被回收的、不可以用的内存)
OC中不存在空指针错误,给空指针发送消息,不报错
故操纵野指针是非常危险的事情。
强行访问一块不可用内存。程序出错(绿色)
EXC_BAD_ACCESS 访问了一块坏的内存(已经被回收的、不可以用的内存)
EXC_BAD_ACCESS(code=1,address=0x20)
🍎 给已经挂掉的僵尸对象发送一条 [对象名 retain] 消息时
🍇 会出现两种情况
- 在开启僵尸对象检测的时候 则程序崩溃。
- 未开启动僵尸对象检测时候 则不会起到任何作用
🍎 开启僵尸对象检测功能时
🍇当给已经释放的对象发送消息是会导致程序崩溃(程序闪退、闪退..)
例如:
Person *p = [Person alloc] init];
[p release];
[p setName:@"张三"];
则会报错:
- [Person setName:]:message sent to deallocted instance 0x100109a10
报错原因是:给已经释放的对象发送了一条 - [setName:] 的消息
🍎 没有开启僵尸对象的检测功能
🍇当给已经释放的对象发送消息是会导致程序崩溃
则不会警告、不会报错、也不会崩溃、
例如:
Person *p = [Person alloc] init];
[p release];
[p setName:@"张三"];
在程序运行完毕,没有出现报错,只是setName方法赋值没有成功。
所以,这个消息只是一行空代码而已,没起到任何作用。
property 的内存管理
1.内存管理相关的参数
1> @property (retain) release 一次旧对象 retain 一次新对象 (适用于OC对象类型)
2> @property (assign) 直接赋值 ( 默认的 只适用于非OC对象类型 )
3> @property (copy) release 一次旧对象 拷贝一个新对象出来
2.是否要生成 set 方法
1> @property (readonly) (默认)只会生成 getter 方法的声明和实现
2> @property (readwrite) 同时生成 setter 和 getter 方法的声明和实现
3.多线程管理
1> @property (nonatomic) 性能高
2> @property (atomic) 性能低 (默认)
4.setter 和 getter 方法的名称
1> @property (setter = methd) 决定了set方法的名称 一定要有个冒号 :
2> @property (getter = methd) 决定了get方法的名称 (一般用在BOOL类型)
此方法一般用于 BOOL 类型
例如:
@property (getter = isRich) BOOL rich ;
MRC 内存管理代码规律以及规范
1.你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)
2.你不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release)
3.谁alloc,谁release
4.谁retain,谁release
只要调用了
alloc
,必须有release(autorelease)
对象不是通过alloc
产生的,就不需要release
setter 方法的代码规范
1> 基本数据类型:直接复制
- (void)setAge:(int)age
{
_age = age;
}
2> OC对象类型
- (void)setCar:(Car *)car
{
// 1.先判断是不是新传进来对象
if ( car != _car )
{
// 2. 如果不是同一个对象,对旧对象做一次release
[_car release];
// 3.对新对象做一次retain
_car = [car retain];
}
}
- dealloc 方法的代码规范
1> 一定要[super dealloc],而且放到最后面
2> 对self (当前对象) 所拥有的其他对象做一次release
- (void)dealloc
{
[_name release]; // person 拥有的 _name 对象
[_book release]; // person 拥有的 _book 对象
[super dealloc];
}
四、autorelease
1.系统自带的方法中,如果不包含alloc、new、copy,那么这些方法返回的对象都是已经autorelease过的
例如:
[NSDate date];
[NSString stringWithFormat:....];
2.开发中经常写一些类方法快速创建一个autorelease的对象
- 创建对象的时候不要直接使用类名,用self
例如 Person 类
+ (instancetype)person
{
return [[self alloc] init] autorelease];
}
ARC
- 指针分2种
<1> 强指针: 默认情况下,所有的指针都是强指针
__strong
<2> 弱指针:__weak
当没有强指针指向对象的时候弱指针指向的对象就会被回收掉
- ARC的判断准则: 只要没有强指针指向对象,对象就会被ARC机制释放掉
<1> 不允许调用
release
、retain
、retainCount
<2> 允许重写dealloc
但是不允许调用[super dealloc];
<3>@property
的参数
(strong)
意味着成员变量是个强指针 / 相当于原来的retain
(适用于任何OC
类型)
(weak)
意味着成员变量是个弱指针 / 相当于原来的assign
(适用于任何OC
类型)
(assig)
适用于非OC
对象类型
<4>MRC
时用的retain
全部改为strong
- 关于对象循环引用的问题
当两端循环引用的时候,解决方案:
在.h
中使用@class ClassName;
ARC
一端用strong,另一端用weak
MRC
一端用retain 一端用assgin