Obj-C 内存管理


内存管理是任何编程语言中最重要的过程之一。这是在需要对象时分配对象内存并在不再需要时释放对象内存的过程。

管理对象内存是一个性能问题;如果应用程序不释放不需要的对象,其内存占用就会增加并且性能会受到影响。

Objective-C 内存管理技术大致可以分为两类。

  • “手动保留-释放”或 MRR
  • “自动引用计数”或 ARC

“手动保留-释放”或 MRR

在 MRR 中,我们通过自己跟踪对象来显式管理内存。这是使用称为引用计数的模型来实现的,该模型是基础类 NSObject 与运行时环境结合提供的。

MRR 和 ARC 唯一的区别是前者的保留和释放是由我们手动处理的,而后者是自动处理的。

下图展示了 Objective-C 中内存管理如何工作的示例。

Objective-C 内存管理

A类对象的内存生命周期如上图所示。可以看到,保留计数显示在对象下方,当对象的保留计数变为0时,该对象被完全释放,其内存被释放以供其他对象使用。

A 类对象首先使用 NSObject 中可用的 alloc/init 方法创建。现在,保留计数变为 1。

现在,B类保留了A类的对象,并且A类对象的保留计数变为2。

然后,C 类复制该对象。现在,它被创建为 A 类的另一个实例,实例变量具有相同的值。这里,保留计数为1,而不是原始对象的保留计数。这由图中的虚线表示。

复制的对象被C类使用release方法释放,保留计数变为0,因此该对象被销毁。

对于初始 A 类对象,保留计数为 2,并且必须释放两次才能销毁它。这是通过 A 类和 B 类的释放语句来完成的,它将保留计数分别减少到 1 和 0。最后,对象被销毁。

MRR基本规则

  • 我们拥有我们创建的任何对象:我们使用名称以“alloc”、“new”、“copy”或“mutableCopy”开头的方法创建一个对象

  • 我们可以使用保留来获取对象的所有权:通常保证接收到的对象在接收它的方法中保持有效,并且该方法还可以安全地将对象返回给其调用者。我们在两种情况下使用保留 -

    • 在访问器方法或 init 方法的实现中,获取我们想要存储为属性值的对象的所有权。

    • 防止对象因其他操作的副作用而失效。

  • 当我们不再需要它时,我们必须放弃对我们拥有的对象的所有权:我们通过向对象发送释放消息或自动释放消息来放弃对对象的所有权。因此,在 Cocoa 术语中,放弃对象的所有权通常被称为“释放”对象。

  • 您不得放弃不属于您的对象的所有权:这只是之前明确规定的政策规则的必然结果。

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

- (void)dealloc  {
  NSLog(@"Object deallocated");
  [super dealloc];
}

@end

int main() {
   
   /* my first program in Objective-C */
   SampleClass *sampleClass = [[SampleClass alloc]init];
   [sampleClass sampleMethod];
   
   NSLog(@"Retain Count after initial allocation: %d", 
   [sampleClass retainCount]);
   [sampleClass retain];
   
   NSLog(@"Retain Count after retain: %d", [sampleClass retainCount]);
   [sampleClass release];
   NSLog(@"Retain Count after release: %d", [sampleClass retainCount]);
   [sampleClass release];
   NSLog(@"SampleClass dealloc will be called before this");
   
   // Should set the object to nil
   sampleClass = nil;
   return 0;
}

当我们编译上面的程序时,我们将得到以下输出。

2013-09-28 04:39:52.310 demo[8385] Hello, World!
2013-09-28 04:39:52.311 demo[8385] Retain Count after initial allocation: 1
2013-09-28 04:39:52.311 demo[8385] Retain Count after retain: 2
2013-09-28 04:39:52.311 demo[8385] Retain Count after release: 1
2013-09-28 04:39:52.311 demo[8385] Object deallocated
2013-09-28 04:39:52.311 demo[8385] SampleClass dealloc will be called before this

“自动引用计数”或 ARC

在自动引用计数或ARC中,系统使用与MRR相同的引用计数系统,但它在编译时为我们插入适当的内存管理方法调用。强烈鼓励我们在新项目中使用 ARC。如果我们使用 ARC,通常不需要了解本文档中描述的底层实现,尽管它在某些情况下可能会有所帮助。有关 ARC 的更多信息,请参阅过渡到 ARC 发行说明。

如上所述,在 ARC 中,我们不需要添加release和retain方法,因为这将由编译器处理。其实Objective-C的底层流程还是一样的。它内部使用了保留和释放操作,使开发人员更容易编码,而无需担心这些操作,这将减少编写的代码量和内存泄漏的可能性。

还有另一个原则称为垃圾收集,它与 MRR 一起在 Mac OS-X 中使用,但自从 OS-X Mountain Lion 中弃用后,它就没有与 MRR 一起讨论。此外,iOS 对象从来没有垃圾收集功能。对于 ARC,OS-X 中也不再使用垃圾收集。

这是一个简单的 ARC 示例。请注意,这不适用于在线编译器,因为它不支持 ARC。

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

- (void)dealloc  {
  NSLog(@"Object deallocated");
}

@end

int main() {
   /* my first program in Objective-C */
   @autoreleasepool {
      SampleClass *sampleClass = [[SampleClass alloc]init];
      [sampleClass sampleMethod];
      sampleClass = nil;
   }
   return 0;
}

当我们编译上面的程序时,我们将得到以下输出。

2013-09-28 04:45:47.310 demo[8385] Hello, World!
2013-09-28 04:45:47.311 demo[8385] Object deallocated