————————Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ———————
之前我们已经提到过C语言中的内存存储空间。有五大区域:堆、栈、自由存储区、全局\静态存储区和常量存储区。
OC是以C语言为基础的,所以也分这五大区域。
下面我们讨论一下OC中内存管理的规律。
一、引用计数器
1.每个OC对象都有自己的引用计数器,是一个整数,即对象被引用的次数。
该计数器一旦诞生,次数就为1,当计数器为0时,该存储空间就消失了。
所以我们要操纵该计数器来控制内存。
2.引用计数器的作用
1)当使用alloc、new或copy创建一个新对象时,新对象的引用计数器默认就是1;
2)当一个对象的引用计数器为0时,对象占用的内存就会被系统回收,即若对象的计数器不为0,那么在整个程序运行过程中,它占用的内存就不可能被回收,除非整个程序已经推出。
3.引用计数器的操作
1)给对象发送一条retain消息(即调用retain方法),可以使引用计数器+1(retain方法返回对象本身)
[p retain];
2)给对象发送一条release消息可使引用计数器-1
[p release];
3)可给对象发送retainCount消息获得当前的引用计数器值
[p retainCount];
4.对象的销毁
1)当一个对象的引用计数器值为0时,那么它将销毁,其占用的内存被系统回收
2)当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
3)一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后调用
4)不要直接调用dealloc方法
5)一旦对象被回收了,它占用的内存就不可再用,坚持用回导致程序崩溃。(这时野指针错误)
总结:为了更好地控制内存管理,有1个alloc就有1个release,有1个retain,就有1个release。
下面我们了解一下什么是野指针。
二、野指针和空指针
当对象的引用计数器值为0,该对象销毁,称为僵尸对象。这时,p为野指针:指向僵尸对象(不可用的内存)的指针。
在引用计数器为0时,还调用[p release];,则运行报错。
总结:
1.只要想用这个对象,就让对象计数器+1
2.当不再用这个对象,计数器-1
3.谁创建,谁release
1)如果通过alloc、new或[mutable]copy来创建一个对象,那你必须调用release或autorelease;----->所以不是你创建的,就不用你去[auto]release
4.谁retain,谁release
只要你调用了retain,无论这个对象如何生成的,你都要调用release
总之,就是要:有始有终,有加就有减。
三、内存管理代码规范
1.只要调用了alloc,必须有release(autorelease)
2.set方法的代码规范
(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];
}
}
3.dealloc方法的代码规范
1)一定要[super dealloc],且放到最后面
2)对当前对象所拥有的其他对象做一次release
- (void)dealloc
{
[_car release];
[super dealloc];
}
四、@property内存管理
1.@property的内存管理
@property (retain)Book *book;
就相当于
- (void)setBook:(Book *)book
{
if(_book !=book)
{
[_book release];
_book = [book retain];
}
}
retain的作用是生成的set方法里面,release旧值,retain新值
2.@property的参数
@property(retain)int age;这样写是不对的,因为基本数据类型不需要内存控制,因为会跟着程序块自动清除。而OC类型不会。
1)set方法内存管理相关的参数
retain : release旧值,retain新值(适用于OC对象类型)
assign : 直接赋值(默认,适用于非OC对象类型)
copy : release旧值,copy新值
2)是否要生成set方法
readwrite : 同时生成setter和getter的声明、实现(默认)
readonly : 只会生成getter的声明、实现
3.多线程管理
nonatomic : 性能高 (一般就用这个)
atomic : 性能低(默认)
4.setter和getter方法的名称
setter : 决定了set方法的名称,一定要有个冒号 :
getter : 决定了get方法的名称(一般用在BOOL类型)
五、@class和循环retain
1.@class的用法
@class Card;
该语句仅仅告诉编译器,Card是一个类,但不知道Card类中又什么变量。
一般情况下,如果两个类,你有我,我有你,一般两边都用@class xxx;,所以涉及到循环引用就用@class。
但是@class比import少一个功能,即没有将所有的功能拷贝过来。一般在日常开发中,如果要到.h文件,则将#import "Person.h"放到.m文件中声明。
思考题:
为什么开发中用@class而不用#import(除了NSObject用#import)?(@class与#import区别)
如果有上百个文件#import同一个文件或者这些文件依次被#import,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样效率很低,而用@class就不会出现这种问题。
2.循环retain
比如A对象retain了B对象,B对象retain了A对象,这样会导致A对象和B对象永远无法释放。
遇到这种问题的解决办法就是:当两端互相利用时,应该一端用retain,一端用assign
autorelease是半自动释放内存,实际上只是延迟了释放的时间。
基本格式:
@autoreleasepool
{ //开始代表创建了释放池
......
} //结束代表销毁释放池
缺点:不能精确控制释放时间,所以不能乱用,所以尽量用release来精确控制。
因此,autorelease一般适用于占用内存比较小的对象。
总结:
1.autorelease的基本用法
1)会将对象放到一个自动释放池中
2)当自动释放池被销毁时,会对池子里面的所有对象做一次release操作(不是销毁对象,只是做-1操作)
3)会返回对象本身
4)调用完autorelease后,对象的计数器不变
2.autorelease好处
1)不用再关心对象释放的时间
2)不用再关心什么时候调用release
3.autorelease的使用注意
1)占用内存较大的对象不要随便使用autorelease
2)占用内存较小的对象使用autorelease,没有太大影响
4.错误写法
1)alloc之后调用autorelease,又调用release
2)连续调用多次autorelease
5.自动释放池
1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在的。(先进来的后出去)
2)当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
6.autorelease使用细节
一个方法可以创建出不同类型的对象(谁调用我,我就创建谁);创建对象时,尽量考虑是否可以使用self,尽量用self。
注:没有autoreleasepool,autorelease是没有作用的。
七、ARC机制(Automatic Reference Counting 自动引用计数)
ARC机制是一个编译器特性,这与Java中的垃圾回收是不一样的,Java中的垃圾回收是运行时特性。
1.ARC原理
int main()
{
Person *p = [[Person alloc]init];
p = [[Person alloc]init]; //这一行就释放Person对象的存储空间
NSLog(@"————");
return 0;
}
释放内存的位置是当强指针p指针不再指向对象的时候,这时对象就会销毁。
总结:
ARC的判断准则:只要没有强指针指向对象,就会释放对象。
指针分两种:(1)强指针:默认情况下,所有的指针都是强指针(__strong);(2)弱指针:__weak
ARC中,只需将之前的retain换成strong,例如:
@property (nonatomic, strong)Dog *dog;
若写为retain,但dealloc中不能用release了。
因此,在ARC中不允许调用release、retain、retainCount,允许重写dealloc,但不允许调用[super dealloc];
@property的参数
*strong:成员变量是强指针(适用于OC对象类型)
*weak:成员变量是弱对象(适用于OC对象类型)
*assign:适用于非OC对象类型
注意:ARC的循环应用问题
这个问题和循环retain问题是一样的。解决办法也是类似的。
解决方案:
当两端循环引用的时候,(1)ARC,一端用strong,一端用weak;(2)非ARC,一端用retain,一端用assign。