Objective-C 高级内存管理

Objective-C 提供了两种内存管理的方法,一是 ARC ,即 Automatic Reference Counting,二是 MRR,即 Manual Retain-release。苹果官方推荐的方法是 ARC,因为使用 ARC 时,我们不需要考虑这些复杂的管理过程,编译器会在编译时自动帮我们加上对应的代码。

虽然 ARC 很方便,但是作为一名开发者,最好对内存的管理原理有所了解。因此我们来看看 MRR 下,应该如何管理内存。

首先在 MRR 环境下,如果从引用计数的角度来思考内存管理,那么往往会适得其反。 苹果推荐的思考方式是考虑对象的所有权和对象图。 object graphs

内存管理策略

内存管理模型是基于对象的所有权的。一个对象可以有一个或者多个所有者。只要一个对象的拥有者不为零,那么它就可以继续存在。如果一个对象没被其他对象所拥有,那么运行时就会自动摧毁它。为了确定你何时拥有一个对象,Cocoa制订了以下的内存管理策略:

  • 自己生成的对象,自己持有。
  • 非自己生成的对象,自己也能持有。
  • 不再需要自己持有的对象时释放。
  • 非自己持有的对象无法释放。

使用 autorelease 来设置延迟释放

看下面这个例子:

因为创建对象时使用的是 alloc方法,所以你拥有生成的 string。那么按照内存管理策略,你应该在不使用它时进行释放。如果使用release,即下面这样:

那么 string 在 fullName 未返回前就已经被释放了,因此返回的是一个无效的对象。如果使用 autorelease,那么表明这个对象仍然需要被释放,但是它的释放过程被推迟到了函数返回之后。

通过引用返回的值,无法被拥有。

Cocoa中的一些方法通过引用来返回一些值,它们通常都接收类型为 ClassName ** 和 id * 的参数。一个典型例子就是 NSError。

因为NSError并不是由你创建的,所以不需要由你来释放它。

实用的内存管理

使用访问器方法来使内存管理更加简单

如果一个类有一个对象型属性,那么在对它进行赋值时,必须确保所赋的值在使用时仍然存在,因此,你需要在赋值时声明对所赋的对象的拥有权,并且释放掉当前所拥有的那个值。

虽然看起来很复杂多余,但是如果你一直使用访问器方法,那么内存管理出错的可能性就会大大降低。相反,如果一直对实例变量调用 retain 和 release 方法,那么十有八九你做错了。

考虑下面的例子:

count属性会声明两个访问器方法,分别是- (NSNumber *)count- (void) setCount:(NSNumber *) newCount

使用访问器方法来设置属性值

假设需要实现一个方法来重置计数器。 第一种方法:

第二种方法:

这种方法使用了便捷构造器来创建一个新的 NSNumber 对象,因此不需要发送 retain 或 release 消息。

第三种方法:

对于简单的情况这种方法可以奏效,但是在某些情况下,这种对实例变量进行 release 和 retain 的方法一定会带来问题。

不要在初始化和 dealloc 方法中使用构造器方法

初始化和 dealloc 方法是唯二的两个不可以使用构造器方法的地方。 为了初始化count属性的值,你应该这么写:

或者这样:

因为 count 是一个对象型属性,因此需要在 dealloc 方法中进行释放。

(注:这条原则不是很明白,难道在初始化方法中不能写成
self.count = [starting copy];吗)

使用弱引用来避免 retain cycle

retain cycle,简而言之就是两个对象A和B,相互持有对对方的强引用,这样A释放时需要B先释放,而B释放时又需要A先释放,因此A和B一直在等待对方释放而一直释放不了,因此形成了一个环。

retain cycle

避免 retain cycle 的方法是将其中一个对象对另一个对象的引用设置为弱引用。

避免造成你正在使用的对象的释放

Cocoa 的拥有权策略(这段话不是很好翻译,因此保持原状):

Cocoa’s ownership policy specifies that received objects should typically remain valid throughout the scope of the calling method. It should also be possible to return a received object from the current scope without fear of it being released. It should not matter to your application that the getter method of an object returns a cached instance variable or a computed value. What matters is that the object remains valid for the time you need it.

对于这个策略,也存在几个特例,可以分为以下两类:

  1. 当一个对象从基础集合类中移除时

    当从一个基础集合类中移除一个对象时,这个对象会收到一条 release 消息。如果集合是这个对象的唯一拥有者,那么这个对象会立即被销毁。

  2. 当一个“父亲”对象被销毁时

    当从一个对象中获取另一个对象,即第一个对象是第二个对象的”父亲“时(这里的父亲并不是类继承中的继承关系,只是一种形象的说明),如果 parent 被销毁,并且 parent 是 child 的唯一拥有者时,那么 child 也会在同时被销毁。

为了避免以上两种情形,可以在获得 heisenObject 后立即 retain,在使用完它之后立即 release。

不要使用 dealloc 来管理稀有资源

你不应该在 dealloc 方法中管理一些稀有的资源,比如文件描述符、网络连接、缓冲区和缓存等。特别地,永远不要直接使用 dealloc 方法。

集合拥有它们包含的对象

当向一个集合,例如数组、词典、集中添加对象时,集合会获得对这个对象的所有权。因此当从集合中移除这个对象,或者集合本身被释放时,集合会放弃对此对象的所有权。

在上面的代码中,因为没有使用 alloc 来给对象分配空间,因此不需要调用 release。同时也没有必要为加入 array 的对象调用 retain,因为 array 会自动这么做。

在这个代码中,因为使用了 alloc 方法,因此必须调用 release 方法来释放分配的空间。因为 array 会自动持有添加的对象,因此不需要手工调用 retain 方法。

拥有权策略是通过引用计数实现的

  • 当创建一个对象时,它的引用计数+1。
  • 当给一个对象发送 retain 消息时,它的引用计数自动+1。
  • 当给一个对象发送 release 消息时,它的引用计数自动-1。
  • 当给一个对象发送 autorelease 消息时,它的引用计数会在当前的自动释放池结束时+1。
  • 如果一个对象的引用计数变为0,那么它会被销毁。

使用自动释放池

自动释放池在上面也有提到,它的作用是自动释放在其中声明的变量。

自动释放池使用@autorelease来标记。@autorelease后是一对大括号,自动释放池的生命周期的范围就是这个大括号。

在自动释放池的最后,在池子中声明和使用的变量会收到一条 release 消息。

和其他代码块一样,自动释放池的代码块也可以重叠起来,如下所示:

一般说来,我们不需要直接和自动释放池打交道,不过也有一些特殊情况需要我们自己来创建自动释放池。这些例外情况如下:

  1. 所编写的程序并不是基于 UI 框架,而是一个命令行工具。
  2. 写了一个循环,在这个循环中会产生大量的临时变量。
  3. 产生了第二个线程。

下面两节将对以上的特殊情况进行说明。

使用局部自动释放池来减少内存占用

在大量产生 autorelease 的对象时,只要 自动释放池不被释放,那么生成的对象也不能被释放,因此会导致设备内存不足的现象。典型的例子是从文件系统中读入大量图片并改变其尺寸。

因此这种情况下有必要在适当的位置生成、持有和废弃自动释放池。

自动释放池和线程

Each thread in a Cocoa application maintains its own stack of autorelease pool blocks. If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.

If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据