Objective-C 内存管理

内存管理是任何编程语言中最重要的过程之一。对象的内存在需要时进行分配,不再需要时解除分配。

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

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

  • 手动保留释放(Manual Retain-Release,即 MRR)
  • 自动引用计算(Automatic Reference Counting,即 ARC)

Manual Retain-Release(手动保留释放)

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

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

下图是 Objective-C中 内存管理工作的实例:

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

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

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

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

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

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


MRR 基本规则

  • 我们拥有我们创建的任何对象:我们使用名称以 "alloc", "new", "copy", 或 "mutableCopy" 开头的方法创建对象。
  • 我们可以使用 retain 获得对象的所有权:接收到的对象通常保证在其接收到的方法中保持有效,并且该方法还可以安全地将对象返回给其调用方。我们在两种情况下使用 retain:
    • 在访问器方法或init方法的实现中,获取要存储为属性值的对象的所有权。
    • 以防止对象由于某些其他操作的副作用而无效。
  • 当我们不再需要它时,我们必须放弃我们拥有的对象的所有权:我们通过发送释放消息或自动释放消息来放弃对象的所有权。因此,在 Cocoa 术语中,放弃对象的所有权通常被称为 "releasing" 释放对象。
  • 您不能放弃您不拥有的对象的所有权:这只是前面明确声明的策略规则的必然结果。
  1. #import <Foundation/Foundation.h>
  2. @interface SampleClass:NSObject
  3. - (void)sampleMethod;
  4. @end
  5. @implementation SampleClass
  6. - (void)sampleMethod {
  7. NSLog(@"Hello, World! \n");
  8. }
  9. - (void)dealloc {
  10. NSLog(@"Object deallocated");
  11. [super dealloc];
  12. }
  13. @end
  14. int main() {
  15. /* my first program in Objective-C */
  16. SampleClass *sampleClass = [[SampleClass alloc]init];
  17. [sampleClass sampleMethod];
  18. NSLog(@"Retain Count after initial allocation: %d",
  19. [sampleClass retainCount]);
  20. [sampleClass retain];
  21. NSLog(@"Retain Count after retain: %d", [sampleClass retainCount]);
  22. [sampleClass release];
  23. NSLog(@"Retain Count after release: %d", [sampleClass retainCount]);
  24. [sampleClass release];
  25. NSLog(@"SampleClass dealloc will be called before this");
  26. // Should set the object to nil
  27. sampleClass = nil;
  28. return 0;
  29. }

结果如下:

  1. 2022-07-07 12:39:52.310 demo[8385] Hello, World!
  2. 2022-07-07 12:39:52.311 demo[8385] Retain Count after initial allocation: 1
  3. 2022-07-07 12:39:52.311 demo[8385] Retain Count after retain: 2
  4. 2022-07-07 12:39:52.311 demo[8385] Retain Count after release: 1
  5. 2022-07-07 12:39:52.311 demo[8385] Object deallocated
  6. 2022-07-07 12:39:52.311 demo[8385] SampleClass dealloc will be called before this

自动引用计算(Automatic Reference Counting)

自动引用计算(ARC)中,系统使用与 MRR 相同的引用计数系统,但它在编译时为我们插入适当的内存管理方法调用。我们强烈鼓励在新项目中使用 ARC。如果我们使用 ARC,通常不需要理解本文档中描述的底层实现,尽管在某些情况下可能会有所帮助。有关 ARC 的详细信息,请参见 Transitioning to ARC Release Notes.

如上所述,在 ARC 中,我们不需要添加 releaseretain 方法,因为这将由编译器负责。实际上,Objective-C 的基本过程仍然相同。它在内部使用 retainrelease 操作,使开发人员更容易编写代码,而不必担心这些操作,这将减少编写的代码量和内存泄漏的可能性。

还有另一个叫做垃圾收集的原则,它与 MRR 一起在 Mac OS-X 中使用,但由于它在 OS-X Mountain Lion 中被弃用,所以它没有与 MRR 一同讨论。此外,iOS 对象从来没有垃圾收集功能。使用 ARC,在 OS-X 中也没有使用垃圾收集。

下面是一个简单的 ARC 实例。

注意:这在本站的在线编译器上不起作用,因为它不支持 ARC。
  1. #import <Foundation/Foundation.h>
  2. @interface SampleClass:NSObject
  3. - (void)sampleMethod;
  4. @end
  5. @implementation SampleClass
  6. - (void)sampleMethod {
  7. NSLog(@"Hello, World! \n");
  8. }
  9. - (void)dealloc {
  10. NSLog(@"Object deallocated");
  11. }
  12. @end
  13. int main() {
  14. /* my first program in Objective-C */
  15. @autoreleasepool {
  16. SampleClass *sampleClass = [[SampleClass alloc]init];
  17. [sampleClass sampleMethod];
  18. sampleClass = nil;
  19. }
  20. return 0;
  21. }

结果如下:

  1. 2022-07-07 12:45:47.310 demo[8385] Hello, World!
  2. 2022-07-07 12:45:47.311 demo[8385] Object deallocated