教程详细:
技术:Objective-C 难度:初学者 完成时间:20-30分钟
欢迎来到学习Objective-C系列教程的第五部分,今天我们要看看内存管理,这是特意为新手准备的一个Objective-C的章节哟。大部分脚本语言(如PHP)则采取内存自动管理,但Objective-C需要我们慎用内存,需要我们手动创建和释放对象。
这是挺好的做法,这让我们控制多少内存被应用程序使用,这样,你就不会遇到系统内存的泄漏了。更重要的是,移动系统,例如iPhone的内存比台式机器更有限。
两种方式
在Objective-C中有两种内存管理方式,第一种是引用计数,第二种是垃圾回收。你可以认为是手动和自动两种,因为计数引用是用程序代码实现的,垃圾回收是系统自动管理内存的。有一点要注意的是iPhone没有垃圾回收功能,这就是为什么我们不深究其工作的原因。如果你的程序是运行Mac上,你可以在苹果的文档上获释垃圾回收的工作原理。
引用计数
嗯~,我们该如何管理内存呢?首先,什么时候在我们的代码里使用到内存?当你创建一个类的实例时,内存就被申请了并且我们能使用它。现在,一个小小的对象也许还不能体现大大的关注,但当你的应用程序规模大起来时,它马上要引起注意的了。
让我们先看看一个例子吧,我们有一些绘画的应用程序,每个图形的形状是单独的对象。如果用户画了100个形状,这样在内存里就有一百个对象了。现在,我们的用户重新开始并清除屏幕后,接着画另外100个对象。如果我们没有管理好我们的内存,我们将有200个对象占着内存而不做任何事情。
我们用引用计数来进行计算。当我们创建一新对象并申请内存,我们的对象就保留1个计数。如果我们要求保留这对象,则计数器就变为2。如果我们释放这个对象,这计数器就变回1。但当计数器变为0时,系统就会回收这对象,释放内存。
语法
有几种可影响我们的内存的方法可供调用。首先,当我们用一名字包含alloc,新建,复制等方法来创建一对象时,如果你使用对象来保存方法,这也是真实的。当你手动释放或自动释放一对象时,你不再需要关心对象的所有权发生如何变化了。
所以,如果我们为一对象申请内存:
1: myCarClass *car = [myCarClass alloc];
现在我们要对汽车对象负责,并且必须手动释放它(或自动释放)。这点很重要,如果你尝试手动释放一个被设置为自动释放的对象时,应用程序可能会崩溃的哟。
既然我们用alloc创建了一对象,我们的car对象现在保存计数为1,这意味着它不会被释放。如果也是我们保存的对象,这样:
1: [car retain];
现在我们的内存计数为2。然而为了释放对象,我们需要释放两次来设置计数为0。由于现在的计数为0,这对象就会被清理了。
自动释放和自动释放池(Autorelease and Autorelease Pool’s)
当你已经新建一个Xcode项目,你可能也注意到一些默认产生的关于自动释放池的代码,可能你现在已经忽略它了,那让我们来看看它做什么和在哪里使用它吧。
你的代码可能看起来和这些相似吧:
1: NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
2: [pool drain];
注意:如果你指的是旧文档,你可能会看到最后一行作为释放,这是语言的新特性,但基本上做同样的事情。
现在,你应该能知道上面的代码是做什么的了吧,它是创建一个叫pool的NSAutoReleasePool实例,为它分配内存并用init方法来初使化。
当我们发送autorelease消息给对象时,该对象被添加到内部最深处的自动释放池(内部最深,因为池是可以相互嵌套的,后面会更多谈到)。当池接收到销毁消息,那么所有的接收到自动释放的对象会被释放掉,基于延迟自动释放会在后面介绍。
这是有用的,因为很多方法会返回一个对象,通常返回一个自动释放对象,这意味着我们不用关心对象计数,我们只管使用,不用管释放的问题了,因为它自己完成的。
嵌套的自动释放池
之前我提及过嵌套自动释放池,但我们用这干什么呢?虽然有几种用法,其中一最普通的用法是,在一个使用临时对象的循环里使用嵌套的自动释放池。
例如,你有一用来创建两个临时对象的循环,如果你设置这两个对象为自动释放,你可以使用它们直到循环发送销毁消息,并且不用关心手动释放内存分配。苹果公司有一个很好的例子,当你在文档里使用嵌套的自动释放池就会发现:
1: void main()
2: {
3: NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
4:
5: NSArray *args = [[NSProcessInfo processInfo] arguments];
6:
7: for (NSString *fileName in args) {
8:
9: NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
10:
11: NSError *error = nil;
12: NSString *fileContents = [[[NSString alloc] initWithContentsOfFile:fileName
13: encoding:NSUTF8StringEncoding error:&error] autorelease];
14:
15: /* Process the string, creating and autoreleasing more objects. */
16:
17: [loopPool drain];
18: }
19:
20: /* Do whatever cleanup is needed. */
21: [pool drain];
22:
23: exit (EXIT_SUCCESS);
24: }
上面的例子有一点多余,但这结构是需要的。正如你看到的,当应用程序被打开并且main函数被加载,一个名叫pool的自动释放池就被创建。意味着,在pool发送销毁消息之前所有被自动释放的对象将会被分配到自动释放池中,除非它是已经存在自动释放池里(抱歉,这听起来让人费解)。
在循环中,另外一个叫loopPool的自动释放池被创建。这个池会在循环里被销毁,所以所有在循环里自动释放的对象会在循环结束前被释放掉。
内层里的自动释放池是不会影响到外层自动释放池的,你也许根据需要内嵌许多自动释放池。如果我们在循环里使用释放自动,我们并没有一个单独的自动释放池,那么,我们创建的所有对象不会被释放,直到main函数结束。所以,假如循环100次,我们会有100个对象占用内存,这会使应用程序彭胀的。
保存计数
在我们结束之前,让我们看看一些对更容易管理内存的知识。目前呢,当我们创建了对象,我们已经记着对象的引用数量等等,但我们并没有看到一个实际的数字。为了更好的学习,这里有一个可以看清一个对象的引用数的方法,叫retainCount,输出一对象的引用数:
1: NSLog(@"retainCount for car: %d", [car retainCount]);
retainCount方法返回一个整型,所以呢,我们用%d在控制台标记它。极少数实例的retainCount会出错,因此不应该100%依赖于编程,应该依赖于调试,所以一个应用程序运行着不会使用retainCout方法。
小结:
新手们往往会觉得内在管理是一个比较难以掌握的主题,特别是从其他语言过渡来的童鞋们。我们已经介绍了其基本知识,这些已经足够你在编写应用程序时使用的了。
苹果在其开发者网站上有一个超大的开发文档库,当你对今天接触的知识感到模糊时,我强列建议你多阅读。我们一直致力于教程的精简,并让大家更好的理解内存管理,所以就没有太多的扩展了。
像往常一样,欢迎提问。