一,为什么要管理内存
在ios开发中对内存的管理是很重要的,因为手机的内存及其有限,一个程序在运行中如果占用过多内存会影响到手机的运行速度,这也就不是一个好的应用程序。那么如何让程序尽可能少的占用内存呢?最核心的思想就是及时回收不用的对象,程序运行中需要产生很多对象以便完成我们需要的活动,对象一旦创建出来就会在内存中占用空间,当这个对象完成活动不再被我们所需要时要及时把它从内存中清除。
系统是怎么判断一个对象什么时候被回收呢?每一个对象内部都存在一个引用计数器,计算这个对象”被引用的次数”,引用计数器占用四个字节存储空间。
二,内存管理基本操作
当使用alloc,new,或者copy创建一个新对象时,新对象的引用计数器默认是1,当对象的引用计数器为0时,系统就会回收它占用的内存。我们可以对对象的引用计数器进行操作:
给对象发送retain消息会使对象的引用计数器值+1;
给对象发送release消息会使对象的引用计数器值-1;
给对象发送retainCount返回对象当前的引用计数器值。
例如:
Person *p =[[Person alloc] init]; //创建一个Person对象,此时p中引用计数器值为1
[Person retain] //retain返回的是对象本身,p中引用计数器值变成2
[Person release] //计数器值减1
[Person release] //计数器值变成0,系统回收p指向的对象内存
此时被回收了内存的Person对象叫做僵尸对象,僵尸对象不能被操作,p指针指向僵尸对象,此时p指针成为野指针。
三,多对象内存管理set方法管理
单个对象的内存管理很简单,复杂的是涉及到多个有联系的对象时的内存管理,例如:
在Person类实现中有个setBook方法使Person类能使用Book类作为成员变量,同时有book方法返回成员变量_book:
@implementation Person
- (void)setBook(Book *)book
{
_book= book;
}
- (Book *)book
{
return_book;
} //类的声明等其他创建类细节在此省略
Book *b = [[Book alloc] init];
Person *p = [[Person alloc] init];
[p setBook:b]; //内存中的Book对象同时被b和p使用
[b release];
[p book]; //在此当p再调用book方法就会报错,因为在上一步Book对象已经被回收内存,成为了僵尸对象,不能再被调用
由此引出多对象内存管理的规范:
1,想要使用(占用)某个对象时应对对象进行一次retain操作
2,不再使用(占用)某个对象时应对对象进行一次release操作
3,即:谁retain谁release
因此应对set方法进行初步规范:
- (void)setBook(Book *)book
{
_book= [book retain]; //想要使用对象时需进行一次retain操作
}
但是这样的话:
Book *b = [[Book alloc] init]; //b被创建引用计数器值为1
Person *p = [[Person alloc] init]; //p被创建引用计数器值为1
[p setBook:b]; //b被p引用,在set方法中b做一次retain引用计数器值为2
[b release]; //b的引用计数器值减1变成1
[p release]; //p的引用计数器值减1变成0被回收
可见b指向的Book对象并没有被回收,造成这样的原因从内存管理规范中可以看出,谁retain谁release,p对象的set方法中对b进行了一次retain却没有相应的release操作。怎么解决这个问题呢?对象在被系统回收内存时,系统会自动调用一次对象自带的dealloc方法,我们要做的是在类的实现中重写dealloc方法:
- (void)dealloc
{
[_book release]; //当一个对象被回收时,就再用不到成员变量中的类对象,应对成员变量中的类对象进行一次release操作
[superrelease]; //重写dealloc方法时不要忘记要在最后调用父类的dealloc方法
}
这时候观察发现问题:p对象中的set方法每调用一次,就会对成员变量所指向的b对象进行一次retain操作,若p的set方法被多次调用,即不小心多次输入或用set方法改变成员变量指向的对象,如:
Book *b1 = [[Book alloc] init]; //b1被创建引用计数器值为1
Book *b2 = [[Book alloc] init]; //b2被创建引用计数器值为1
Person *p = [[Person alloc] init]; //p被创建引用计数器值为1
[p setBook:b1]; //b1被p引用,在set方法中b1做一次retain引用计数器值为2
[p setBook:b2]; //p中的成员变量改变指向了b2,在set方法中b2做一次retain引用计数器值为2
[b1 release]; //b1的引用计数器值减1变成1
[b2 release]; //b2的引用计数器值减1变成1
[p release]; //p的引用计数器值减1变成0,内存被回收时系统调用我们重写的dealloc方法,对此时成员变量指向的b2对象进行一次release操作,b2引用计数器值变成0也被系统回收
我们发现b1并没有被回收,因此我们在进行set方法中的retain操作之前应进行一次判定,判定成员是否改变了,set方法代码最终完善为:
- (void)setBook(Book *)book
{
if( book != _book ) //1,先判断传进来的是不是新对象
{
[_bookrelease]; //2,对旧对象进行一次release
_book = [bookretain]; //3,对新对象进行一次retain
}
}