Objective-C中内存管理的一些特例
该教程是讨论IOS平台上内存管理规则之外的一些特殊情况,我相信大部分的开发人员可能都没有觉察到。
我们先普及一下Objectivie-C中的内存管理的基本知识,如果你已经比较熟悉了,可以直接跳过该节。Objective-C使用的是引用计数(Reference Counting),引用计数就是对象用一个变量来保存有几个地方(类、方法等)在使用它。当一个对象被创造出来时,它的引用计数(下面我们用retainCount来表示这个值)为1,在应用程序运行的过程中,可能有很多地方都用到了这个对象,凡是用到这个对象时,就将它的retainCount加1,当不用了时,再将其retainCount减1,当对象的retainCount为0时,表示没有人在用这个对象了,系统就会释放这个对象所占用的内存。
当你通过其它方法获得一个对象时,则假设该对象的保留计数值为1, 而且已经被设置为自动释放,你不需要执行任何操作来确保该对象被清理。但是如果你打算在一段时间内拥有该对象,则需要保留(引用计数加1)它,并且在操作完成时释放它(引用计数减1)。 如果你保留了某个对象,你需要最终释放或者自动释放该对象,必须保持保留方法(retain)和释放方法(release/autorelease)的使用次数相等。 只要你理解了这三条规则基本就足够了。但是要理解下面所讲的一些例外情况,仅有这些知识就不够了。下面就让我们开始这段探索之旅吧。使用Xcode使用“IOS”-“Application”-“Single View Application”模板创建一个名为MemoryTest的工程。在ViewController.m文件的viewDidLoad方法中键入下面的代码:
NSString*emptyStr=[NSStringnew];NSLog(@"emptyStrretainCount:%u",emptyStr.retainCount);先想想你认为输出结果是什么?然后看一下运行后Console的输出是什么?我这边的结果是4294967295,一个很大的数,实际上这个数是UINT_MAX,就是无符号整型的最大值。你原先认为它应该输出1对吗?为什么会这样呢?带着这个疑问在看下面的代码,仍旧将其放入viewDidLoad方法中:
NSString*emptyStr1=[NSStringnew];NSString*emptyStr2=[NSStringnew];NSLog(@"emptyStr1address:%p",emptyStr1);NSLog(@"emptyStr2address:%p",emptyStr2);NSLog(@"emptyStr1retainCount:%u",emptyStr1.retainCount);NSLog(@"emptyStr2retainCount:%u",emptyStr2.retainCount);这段代码创建了两个新的空字符串对象,接着输出这两个对象的在内存中的地址和它们的引用计数值。运行一下,查看一下结果,我这边的结果是:
这两个对象的地址竟然一样,这出离我们原先的认识对吗?new方法两次创建的对象竟然一样,这在c++中是绝不可能的。但这是Objective-C,编译器在底层做了一些我们看不见的工作。很显然这两个空字符串对象指针指向的是同一个对象。Objective-C为什么会这么处理呢?这是因为NSString类型的不可变性,就是这种类型的对象一旦创建,就不能改变(增加或删除其中的字符),如果你希望改变对象,那就用NSMutableString类型。NSString的不可变性使得空字符串对象一旦创建,就不能改变,永远是空字符串,而所有的空字符串都是一样的。所以出于效率的考量,Objective-C编译器在底层就让所有创建的空字符串指向内存中的同一个空字符串。并且这个空字符串对象是不可release掉的,因此它的引用计数值就为UINT_MAX,表示这个对象是不可release的,那你可能会问,我如果release UINT_MAX次,是不是就释放掉了,不是的,实际上你向这个对象发送release消息是没有任何效果的。我们换非空字符串试试,输入下面的代码:
NSString*nonEmptyStr1=@"Hello";NSString*nonEmptyStr2=[[NSStringalloc]initWithString:@"Hello"];NSString*nonEmptyStr3=[[NSStringalloc]initWithFormat:@"%@",@"Hello"];NSLog(@"nonEmptyStr1address:%p",nonEmptyStr1);NSLog(@"nonEmptyStr2address:%p",nonEmptyStr2);NSLog(@"nonEmptyStr3address:%p",nonEmptyStr3);NSLog(@"nonEmptyStr1retainCount:%u",nonEmptyStr1.retainCount);NSLog(@"nonEmptyStr2retainCount:%u",nonEmptyStr2.retainCount);NSLog(@"nonEmptyStr3retainCount:%u",nonEmptyStr3.retainCount);下面是在机器上运行结果:
前两个指针仍然是指向同一个对象,原因上面已经解释了。但是第三个不一样,你可以从NSString的不可变性和对象的初始化方式的不同出发想想原因,相信你很快就可以得出结论的。在Objective-C中不唯NSString是不可变对象,还有NSArray和NSDictionary。同样你可以试试下面的代码:
NSArray*emptyArray1=[[NSArrayalloc]init];NSArray*emptyArray2=[[NSArrayalloc]init];NSArray*emptyArray3=[[NSArrayalloc]initWithArray:emptyArray1];NSLog(@"emptyArray1address:%p",emptyArray1);NSLog(@"emptyArray2address:%p",emptyArray2);NSLog(@"emptyArray3address:%p",emptyArray3);NSLog(@"emptyArray1retainCount:%d",emptyArray1.retainCount);NSLog(@"emptyArray2retainCount:%d",emptyArray2.retainCount);NSLog(@"emptyArray3retainCount:%d",emptyArray3.retainCount);NSArray*nonEmptyArray1=[[NSArrayalloc]initWithObjects:@"1",@"2",nil];NSArray*nonEmptyArray2=[[NSArrayalloc]initWithObjects:@"1",@"2",nil];NSLog(@"nonEmptyArray1address:%p",nonEmptyArray1);NSLog(@"nonEmptyArray2address:%p",nonEmptyArray2);NSLog(@"nonEmptyArray1retainCount:%d",nonEmptyArray1.retainCount);NSLog(@"nonEmptyArray2retainCount:%d",nonEmptyArray2.retainCount);NSDictionary*emptyDict1=[[NSDictionaryalloc]init];NSDictionary*emptyDict2=[[NSDictionaryalloc]init];NSLog(@"emptyDict1address:%p",emptyDict1);NSLog(@"emptyDict2address:%p",emptyDict2);NSLog(@"emptyDict1retainCount:%d",emptyDict1.retainCount);NSLog(@"emptyDict2retainCount:%d",emptyDict2.retainCount);通过运行结果来进一步体会一下Objective-C编译器底层的工作机理。按照上面提到过的三条内存管理规则,你new、init、copy得到一个对象,你就必须负责release掉它,但实际上前面提到过的这些语句是个例外:
NSString*s1=[NSStringnew];NSString*s2=[NSStringalloc]initWithString:@"Hello"];NSArray*a=[NSArrayalloc]init]; NSDictionary*dict=[NSDictionaryalloc]init];
就算你不调用release或autorelease方法释放也不会造成内存泄漏,你可以用Instruments检测一下看看是否有内存泄漏。但是虽然如此,我仍强烈建议你按照内存管理三规则来处理。一致的规则不容易让人迷惑,尤其是对阅读你代码的人。本教程的工程文件:MemoryTest.zip本文作者:安海林,软件工程师,诺基亚北京研究院。他的格言是:学问深时意气平!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。