到今天,学习iphone已经有一段不算太短的时间了,对于内存管理也有一些小的理解,这里就小小的总结下。
IPhone对对象的内存管理是通过保留计数来管理的,创建一个对象,这个对象的保留数就是1,每一次Retain对象,就会是对象的保留数增加一个,给对象每发送一个Release函数,该对象的保留数就减少一个,当这个保留数字减少为0(理论值)的时候,该对象的内存就会被系统回收,提高了系统的内存利用率。在测试过程中,调用对象的retainCount消息,就算该对象的保留数字在理论值为0,调用该对象的消息(ratain)消息,返回的值也是1,这个也是我一直不理解的地方,这个时候,当对象的理论保留数为0(理论值)的时候,我们就不能在向该对象发送消息,比如[object release],如果发送,程序就会崩溃。
那么我们如何理解这个保留计数呢?
当我们使用alloc创建一个对象的时候,我们将这个创建的对应赋值给一个变量,这个时候,这个对象的保留计数=1,这个时候我们可以把这个变量看做是这个对象的一个管理员,该变量不但可以去使用这个对象,还可以增加这个对象的保留计数值,那么我们如何去判断,那些变量是这个对象的管理员,那些对象是这个对象的普通用户哪?首先,我们得区分对于一个对象来说,一个对象的管理员和一个对象的普通用户的区别就是在于,一个普通用户仅仅能够使用该对象,但是不能增加该对象的保留计数值。所以,对于那些能够增加对象保留计数值的被赋值变量都可以看做该对象的管理员。
下面谈谈变量的赋值问题:
变量值之间的赋值,iphone使用的开发语言使用的是类似于C的语言,变量之间的赋值传递的是对象的内存地址也就是我们常数说的指针,在传递的过程中赋值过程有些会增加对象的保留计数,有些是不会增加对象的保留计数值的。
关于UINavigationController的push和pop动作导致的保留计数值的变更问题:
Push的时候,会更加该ViewController对象的保留计数值,pop的时候,会减少该对象的保留计数,这个已经经过测试,确实是这种情况。
2011-10-12 今天就到这里,回头继续...
继续
2011-10-12
当一个对象的保留计数>=1,并且没有引用指向他,因此也就是没有办法释放这个对象占用的内存空间,所以这种情况就存在了内存泄露。
比如:
定义了一个类的全局变量,NSarray *m_Array1;
然后在一个方法中有这么一个函数
-(IBAction)DoAction_2
{
NSArray *m_tempArray = [[NSArray alloc] initWithObjects:@"tt",nil];
m_array = m_tempArray;
}
当我们第一次执行这个函数的时候,不会有任何的内存泄露,我们创建了一个NSarray对象,我们假设该对象名称为X_1,并且我们把这个对象赋值给全局变量,因为不是赋值给属性,因此对象的保留计数仍然是1。然后第二次执行该段代码,我们有创建了一个新的对象,我们假设他为X_2,我们将这个对象赋值给m_array全局变量,这个时候,m_array作为一个指针,他指向了对象X_2,所以他就不在指向对象X_1,这个时候,注意了,关键的地方到了,X_1这个对象在内存中还是存在的,但是却没有指向自己的引用(指针),所以应用在后面的操作中是不能够被释放掉的,因此X_1这个对象就变成了垃圾对象,他所占据的内存空间也就成为了内存泄露,这个我们都可以通过XCode自带的内存检测工具Instruments来监测。
那么正确的处理方法哪:
参考代码如下所示:
-(IBAction)DoAction_2
{
if (m_array!=nil)
{
[m_array release];
}
NSArray *m_tempArray = [[NSArray alloc] initWithObjects:@"tt",nil];
m_array = m_tempArray;
}
上面的这段代码则是类的局部变量进行直接赋值的最佳参考代码。
理解上面的这段文字,关键的地方是要理解这段话,当一个对象的保留计数>=1,并且没有任何引用(指针)指向该对象,那么这种情况就会造成内存泄露,泄露的大小就是该对象在内存中所占据的内存大小。
还有一点需要强调的是:类内部的实例变量,在复制之前一定要释放掉之前他引用的对象,然后在重新赋值。
//对自动释放对象在程序中有关内存的相关操作的理解
之前我们autorelease的使用非常的少,一个关键的原因就是没有很透彻的理解 autoRelease 对类对象的理解,今天也希望通过一个小例子,来说明自己对他的理解。
autorelease 消息的作用是让一个普通的对象转换为 可以自动释放的对象,那么自动释放对象的声明周期又是多久?你用什么来保证? 严格的来说,对象归你使用,知道时间循环的下一个事件,这个事件可以是单击按钮,敲击屏幕等等。还有一种情况是,一个函数内的局部自动释放对象,在当前的函数内,该对象都可以使用,一旦这个函数结束调用,那么这个自动释放的对象就要被释放掉乐。
参考下面的代码,
创建一个全局的变量,
NSArray *m_array;
接着调用第一个函数
-(IBAction)DoAction_2
{
if (m_array!=nil)
{
[m_array release];
}
NSArray *m_tempArray = [[[NSArray alloc] initWithObjects:@"tt",nil] autorelease];
m_array = m_tempArray;
}
在该函数内,m_array被设置一个自动释放的对象,然后单击按钮,处罚第二个函数
-(IBAction)DoAction
{
[m_array release];
}
这个时候问题就出现了,程序会崩溃,这个是因为,在第二个函数中,m_array是一个自动释放的对象,这个对象仅仅在第二个函数内部有效,一旦第一个函数调用结束,那么m_array对象就已经自动释放掉了,当我们执行第二个函数向m_array对象发送消息的时候就会报错,导致程序崩溃,因为,我们向一个保留计数为0的对象发送消息,程序当然会崩溃。我们从这个例子中也就了解到了,对于自动释放对象的生命周期。]
以保留属性的释放问题
使用已保留属性时,使用点表示方法赋值将这些属性设置为空值,这个将调用objectivec-c合成自定义的复制方法,并且释放以前复制给该属性的任何对象,假定以前对象的保留计数为+1,那么这次释放将是计数变为0,
self.make = nil;
变量的内存释放问题
使用普通(非属性)实例变量或者给样式属性赋值,在释放的时候发送释放消息。
参考代码:
[salesman release]
sales = [[someClass alloc] init];
这种赋值风格表示,salesman在对象的生命周期内,随时可以指向一个保留计数为+1的对对象,因此在dealloc方法中,必须释放当前复制给salesman的任何对象,从而将计数置为0
[salesman release]
一个相对完整的释放参考代码
-(void)dealloc
{
self,make = nil;
self.name = nil;
self.book = nil;
[salesman release];
}
今天就想到这里,回头继续.....
2011-10-17,今天继续写一段
今天在上班的路上突然想到一个以前可能存在内存泄露的问题,所以一上班就赶紧写个deom测试下,来检验自己的理解,然后避免后续内存泄露问题的发生:
情景:我们创建了一个类A,里面包含一个类B作为类A的成员变量,我们在A中创建了一个函数,用来返回类B的实例对象,那么我们返回的应该是自动释放类型的,还是不是自动释放类型的哪?
经过测试发现,如果我们返回一个自动释放的对象并且我们仅仅将创建的类的实例对象在一定的范围内使用,比如:在调用创建该类实例对象的函数体内使用,
参考代码:
-(IBAction)DoAction
{
m_Testobj = [self CreateObj];
[m_Testobj doAction_2];
}
-(TestObject *)CreateObj
{
TestObject *m_testobj = [[TestObject alloc] init];
return [m_testobj autorelease];
}
这里时候,DoAction函数的调用后执行后是没有问题的,函数正确执行。我们将返回的对象赋值给的变量m_Testobj 是个全局变量,如果这个时候我们在其他的地方使用m_Testobj对象,那么程序就会崩溃,这是为什么哪?。
如果我们返回的是一个自动释放的对象,那么这个对象仅仅在调用CreateObj函数并返回新对象的函数(DoAction)体内是有效的,在其他的地方则是无效的。那么我们如果才能在不改变返回自动释放对象的基础之上修改这个bug哪?方法也很简单,我们仅仅需要执行一行代码即可,参考代码如下所示:
-(IBAction)DoAction
{
m_Testobj = [self CreateObj];
[m_Testobj retain];
[m_Testobj doAction_2];
}
当我们执行DoAction函数后,执行下面的函数:
-(IBAction)DoAction_2
{
[m_Testobj doAction_2];
}
函数是能够正确的运行的。虽然我们返回的是自动方式的对象,并且这个对象在函数DoAction结束的时候就会被释放掉,可是我们添加了一行代码:
[m_Testobj retain];
该行代码的作用就是让对象的引用计数器增加1,这样等函数DoAction执行完成释放m_Testobj对象一次后,该m_Testobj对象的引用计数器的值也不至于减少到0最后被系回收内存,这样我们就可以在其他的地方调用这个m_Testobj全局变量了。
但是这里似乎还是有点问题的,当我们多次执行DoAction函数后,会造成内存泄露,因为m_Testobj在接受新的对象的值的时候,并没有将原来m_Testobj变量指向的对象释放掉,这样就造成了内存泄露,所以我们还需要修改代码,修改后的代码如下所示:
-(IBAction)DoAction
{
if (m_Testobj!=nil)
{
[m_Testobj release];
}
m_Testobj = [self CreateObj];
[m_Testobj retain];
[m_Testobj doAction_2];
}
今天就写到这里,有空继续...
2011-10-17