OC加强DAY01 - 内存管理
内存管理
内存管理的概述
内存是用来存储数据的
- 如何将数据存储到内存中.
- 声明变量就可以将数据存储到内存中
- 存储到内存中的数据应该如何被释放呢?
- 内存中的五大区域
- 局部变量的作用域执行完就回收
- 手动申请的字节空间需要调用free函数来释放
- 一旦初始化,未初始化的全局变量和静态变量就会从BSS段中回收掉.转存到数据段中
- 数据段的数据直到程序结束才回收
- 代码段也是程序结束才回收
- 堆区中的OC对象一定是要回收的
- 如果不回收马上就会占的慢慢的
- 40M 为程序发送一条警告
- 45M 再发送一条警告
- 120M直接杀死.
- 堆区中的OC对象不会自动回收
除了堆区其他区域中的数据,都是系统自动释放的.堆区中的OC对象是不会自动释放的,直到程序结束
- ==内存管理的范围==
只需要管理内存中的OC对象的释放,也就是任何NSObject的对象
- 对象什么时候需要释放呢?
- 如果OC对象有人在使用,千万不能释放
- 只有当这个OC对象无人使用的时候,才可以释放
7. 引用计数器
- 每一个OC对象都有一个属性.叫做retainCount
- 翻译中文:引用计数器
- 类型是: unsigned long 占据8个字节.
- 作用:这个对象有多少个人在使用.
- 当我们新创建一个独享出来的时候,引用计数器的默认值是1
- 当这个对象多一个人使用的时候,就应该先让这个对象的计数器+1
- 当这个对象少一个人使用的时候,就应该让这个对象的引用计数器-1
- 当这个对象的引用计数器值为0的时候,代表这个对象无人使用
- 这个时候,西永就会李奇回收这个对象.
- 回收这个对象的同时,会自动调用这个对象的dealloc
方法
- 如何操作引用计数器
- 为对象发送一条retain消息.那么这个对象的计数器就会+1
- 发送一条releas消息.就会-1
- 发送retainCount消息.就可以得到计数器的值
- 加加减减,当值为0系统就会立即回收,会自动调用这个对象的dealloc方法
- 内存管理的分类
- MRC
- MRC : Manual Reference Counting 手动引用计数
- 加加减减都是程序员手动执行
- ARC
- Automatic Reference Counting 自动引用计数
- 不需要手动计数,程序员不用手动写代码,系统自动的来改变计数器的值
- 今天重点学MRC.
- ==面试100%必考==
- 早期的APP是基于MRC开发的.
- 现在的iOS大牛都是从MRC成长过来的.
- ARC是基于MRC的.
我们以后,写项目:绝大多数都是ARC.
第一个MRC程序
- 从2011年开始,iOS5 苹果退出了ARC Xcode6开始 . 默认就是ARC
- 在对象回收的时候,会自动调用dealloc方法.
为了监视对象的回收,我们重写dealloc方法,重写dealloc方法必须调用父类的dealloc方法.并且放在最后调用
- 测试
- (void)dealloc
{
NSLog(@"名字叫做%@的人挂了",_name);
[super dealloc];
}
[p1 retain]; //1
NSLog(@"%lu",p1.retainCount);
[p1 release]; //2
NSLog(@"%lu",p1.retainCount);
[p1 release]; //1
NSLog(@"%lu",p1.retainCount);
#### 内存管理的规则
1. 内存管理的重点
> 分清楚什么时候位对象发送retain让计数器+1,什么时候发release让计数器-1;
2. ==内存管理的原则==
> 1. 新创建一个对象,计数器的初始值是1.有对象的创建,就必须匹配一个releas
```objc
HMPerson *p1 = [[HMPerson alloc] init];
[p1 release]; //在MRC下,有创建就要有回收
<div class="se-preview-section-delimiter"></div>
在ARC模式下严禁调用dealloc.否则就报错
- 谁负责
[p1 retain]
谁就要负责[p1 release]
- retain 的次数要和 release 相匹配.
- 只有在多一个人使用这个对象的时候才
[p1 retain]
,不要乱用.有始有终,有加就有减.
- 一句话,谁创建,谁release,谁retain,谁release.
野指针和僵尸对象.
- ==野指针==
- C语言中的野指针指:声明一个指针变量,没有为这个指针变量初始化.这个指针变量的值就是垃圾值指向内存当中一块随机的空间.
- OC中的野指针:一个指针指向的对象已经被释放了.那么这个指针就叫做野指针.
HMPerson* p1 = [[HMPerson alloc] init];
[p1 release];//这句话执行完以后p1就是野指针了
<div class="se-preview-section-delimiter"></div>
对象的回收
- 声明一个变量,就是指在内存中申请指定字节数的连休空间来使用.
- 变量的回收就是,变量所占用的空间可以分配给别人,字节中的数据仍然还在
- 对象的回收也是如此,并且系统未将空间分配给别人时,数据也扔在内存中
==僵尸对象==:
指的是已经被回收但是这个对象的数据还在内存中.
HMPerson* p1 = [[HMPerson alloc] init];
[p1 release];//这句话执行完以后p1就是野指针了
[p1 sayHi];
[p1 sayHi];
[p1 sayHi];
[p1 sayHi];//有可能说到这里就挂了,前面还能sayHi
[p1 sayHi];//那么这块空间应该就已经分给别人了.
//僵尸对象有可能可以访问,也有可能不能访问.
<div class="se-preview-section-delimiter"></div>
- 一个对象一旦成为了僵尸对象后,无论如后都不应该使用了.都不能用了!
- 可以开启僵尸对象的实时检测:
- 当我们开启检测,通过一个指针去访问,只要是僵尸对象,就不能访问,无论还在不在.
- 只要访问僵尸对象控制台就输出错误.
- 为什么不默认开启检测呢?
一旦开启检测,每次通过指针访问都会检测,无疑会浪费性能
- 僵尸对象是无法复活的.
- 一个已经被
release
干掉的对象 是不能通过retain
复活的- 僵尸对象的引用计数器确实为0,只不过再访问这个对象就算是1也不代表什么
单个对象的内存管理
- 当我们通过野指针区访问僵尸对象的时候,如果开启了检测一定会报错
- 为了防止报错,在对象release成为野指针以后就为指针赋值一个nil.
- ==指向nil的对象调用方法不会报错,什么都不会法神==
- ==内存泄露==
指的是一个对象没有在该回收的时候回收,而是一直驻留在内存中,直到程序结束的时候才被释放.
- 单个对象发生内存泄露的情况.
- 有对象创建,没有匹配相应的
p1.release//点语法就是调用方法
- retain的次数和release的次数不匹配.
- 在不适当的时候,为指针赋值为nil
- 在方法当中不适当的为传入对象
[per retain]
- 如何避免单个对象发生内存泄露
- 有对象的创建,就必须匹配一个
release
retain
的次数要和release
的次数相同- 不要随便为一个指针赋值为nil,除非是一个野指针
- 在方法中,不要随意的堆闯入的对象进行retain.
多个对象的内存管理
在多个对象中管理 凤姐开车去拉萨
- 当车作为凤姐的属性时,车不能回收.
- 将bmw对象赋值给fengJie对象的
_car
属性,代表对象多一个人使用- set方法做的事情
- (void)setCar:(NSCar *)car
{
//多一个对象调用car就应该为他发送一条retain
_car = [car retain];
//为car传送retain信息,返回值是car本身.
}
<div class="se-preview-section-delimiter"></div>
- 调用者凤姐delloc的时候,就应该为调用的对象release一次.
- (void)delloc
{
NSSlog(@"名字叫做%@的人死了",_name);
[_car release];//调用者死之前就发送一条releas信息,表示不再用了.
} [super delloc];//调用父类的delloc,自行了断.
<div class="se-preview-section-delimiter"></div>
多个对象管理 冰冰开车 换车
//1. 创建奔驰,冰冰
NSPersin* p1 = [NSPerson personWithName:@"冰冰"];
NSCar* benz = [NSCar carWithSpeed:120];
//3. 这个时候有钱了换车保时捷
NSCar* prosche = [NSCar carWithSpeed:300];
p1.car = porsche;
//2. 创建完立即跟一个release
[benz release];
[p1 release];
//4. 冰冰释放了,同时现在开的保时捷释放了,奔驰泄露了
[porsche release];
<div class="se-preview-section-delimiter"></div>
- 当换车的时候就会发生内存泄露
- 所以让set方法换车的时候多做一件事
- 应该先
[_car release];
代表旧车不再使用- 新车
_car = [car retain];
,代表新车多一个人用
dealloc
方法不能忘,还是要重写.多个对象调用一个对象
验证多个对象调用一个对象,刚才的写法是没有问题的
上面的写法还存在一些问题
NSCar* bmw = [NSCar carWithSpeed:200];
NSPerson* xiaoMing = [NSPerson personWithName:@"小明"];
xiaoMing.car = bmw;
[xiaoMing drive];
//在这里release后计数为1
[bmw release];
//2. 改装时速到400
bmw.speed = 400;
//3. 重新赋给小明,这个时候要先release,计数为0,立即成为僵尸对象.
xiaoMing.car = bmw//把僵尸对象给小明赋值就会出错.
[xiaoMing release];//1. 先创建的对象后release
<div class="se-preview-section-delimiter"></div>
- 如果赋值的对象是同一个对象,不需要release.
- 所以set方法要先
if(_car != car)
再进行release. 如果是同一个对象就什么都不用做了.
- 只有属性的类型是一个OC对象的时候才需要这么写
普通C语言类型就不需要了
- (void)setCar:(HMCar *)car
{
if(_car != car)
{
[_car release];
_car = [car retain];
}
}
@property
- 在MRC里面自动生成的setter getter 方法都是直接赋值代码.
- @preperty可以带参数,带不同的参数可以实现一些特别的功能
- 语法
@preperty(参数1,参数2,参数3...)数据类型 名称;
- 与多线程相关的参数可以带
atomic
,nonatomic
.- 与生成setter方法实现相关的参数
assign
,retain
.- 是否生成只读,读写的封装
readwrite
- 指定生成的getter setter方法的名称
- 和多线程相关的参数,加锁不加锁.
atomic
是默认值,不写默认就是他.生成的set get方法是很安全的,但是性能低下nonatomic
生成的方法不安全,但是效率高.
推荐使用nonatomic 首先是要快!!(iOS)一般都是这个
@property(nonatomic) NSString *name
;
- 与生成setter方法相关的参数
- assign:默认值,生成的setter方法的实现中就是直接赋值.仅此`
- retain:生成的setter方法实现中是标准的MRC内存管理代码
@property(nonatomic,retain) NSCarg *car;
- 当属性的类型是oc对象的时候就是用retain,非oc对象就使用release.
- 是否生成只读或者读写的封装
1.readwrite: 默认值, 同时生成getter setter
2.readonly: 只生成get,不生成getter
3.@property(nonatomic,retain,readonly) NSString *name;
- 指定生成的setter getter方法的名称
- 指定生成的getter setter方法的名称
@property(nonatomic,retain,getter = sbsb ,setter = hbh:) NSString *name;
//set带参数,所以set名字后面跟冒号:
<ul><li>(void)hbh:(NSString *)name;</li>
<li>(NSString *)sbsb;
//无论什么情况下都不要去改set方法名字.
//get情况下一般也不该,只有一种
@property(nonatomic,assign,get=isGoodMan) BOOL goodMan
//只有属性的类型是BOOL类型的时候才去改名字,is开头提高代码阅读性.
补充两个重点
与多线程相关的两个参数.
生成的setter方法实现相关的参数:
1. assign : 当属性的类型是非oc对象时 2. retain : 属性是oc对象是 3. 只会自动生成set get 4. dealloc还是自己写 @property(nonatomic,retain)NSString *name; @property(nonatomic,retain)HMCar *car;
同一组参数只能用一个
- 顺序可以换.一般先写nonatomic,再写retai