内存管理与自动引用计数(ARC)

作者:追风剑情 发布于:2019-3-6 13:13 分类:Objective-C

        在Xcode 4.2之前只能手动管理内存,使用保留(retain)、释放(release)、自动释放(autorelease)。Xcode 4.2版本开始引入自动引用计数(Automatic Reference Counting, ARC)。

提供给Objective-C程序员的基本内存管理模型有以下3种:
  • 自动垃圾收集
  • 手工引用计数和自动释放池
  • 自动引用计数(ARC)

自动垃圾收集

        在Objective-C 2.0中,有一种称为垃圾收集的内存管理形式。iOS运行环境并不支持垃圾收集。在OS X 10.8(Mountain Lion)中垃圾收集已经不推荐使用(未来不会继续支持这项特性)。Apple不建议使用垃圾收集,建议使用ARC进行替代。

手工管理内存计数

[obj retain]; //obj的引用计数增加1

[obj release]; //obj的引用计数减少1

当引用计数为0时可以调用[obj dealloc]来释放它占用的内存。通常需要覆写NSObject的dealloc方法。对象的一些API也会影响引用的数量。

//覆写dealloc方法
-(void) dealloc {
    [data release]; //释放自己的对象和数据
    [super dealloc];//释放任何继承来的对象
}

对象引用和自动释放池

自动释放池用来解决需要延迟释放对象的情况,例如,当方法需要返回一个对象时(return [obj autorelease];)。

[result autorelease]; //将一个对象添加到由自动释放池维护的对象列表中

// 创建一个自动释放池
@autoreleasepool {
    statements
}

注意:
1、自动释放池只包含对象的引用。
2、并不是所有新创建的对象都会被添加到自动释放池中,事实上,任何由以alloc、copy、mutableCopy和new为前缀的方法创建的对象都不会被自动释放。需要在使用完这些对象后手动释放(即,主动给这些对象发送release消息,或者给对象发送autorelease消息将对象添加到自动释放池中)。

事件循环和内存分配

Cocoa和iOS应用运行在所谓的事件循环中。在每次事件发生并处理完事件后都会清理自动释放池,除非对象使用retain,才能从清空自动释放池的过程中幸存下来。
@property (nonatomic, retain) NSMutableArray *data;
retain特性仅会被未使用ARC编译的代码识别。
在手工引用计数环境中,可以为属性添加atomic(默认)或nonatomic特性,也可以添加assign(默认)、retain、copy特性。
atomic: 线程安全
nonatomic: 非线程安全

assign特性的setter实现
self.property = newValue;

retain特性的setter实现
if (property != newValue) {
   [property release];
   property = [newValue retain];
}

copy特性的setter实现
if (property != newValue) {
   [property release];
   property = [newValue copy];
}

手工内存管理规则总结

下面是一些不使用垃圾收集或ARC编译的项目规划:

  • 如果需要保持一个对象不被销毁,可以使用retain。在使用完对象后,需要使用release进行释放。
  • 给对象发送release消息并不会必须销毁这个对象,只当这个对象的引用计数减至0时,对象才会被销毁。然后系统会发送dealloc消息给这个对象用于释放它的内存。
  • 对使用了retain或者copy、mutableCopy、alloc或new方法的任何对象,以及具有retain和copy特性的属性进行释放,需要覆盖dealloc方法,使得在对象被释放的时候能够释放这些实例变量。
  • 在自动释放池被清空时也会为自动释放的对象做些事情。系统每次都会在自动释放池被释放时发送release消息给池中的每个对象。如果池中的对象引用计数降为0,系统会发送dealloc消息销毁这个对象。
  • 如果在方法中不再需要用到这个对象,但需要将其返回,可以给这个对象发送autorelease消息用以标记这个对象延迟释放。autorelease消息并不会影响到对象的引用计数。
  • 当应用终止时,内存中的所有对象都会被释放,不论它们是否在自动释放池中。
  • 当开发Cocoa和iOS应用程序时,随着应用程序的运行,自动释放池会被创建和清空(每次的事件都会发生)在这种情况下,如果要使自动释放池被清空后自动释放的对象还能够存在,对象需要使用retain方法,只要这些对象的引用计数大于发送autorelease消息的数量,就能够在池清理后生存下来。


自动引用计数(ARC)

使用ARC时,系统会检测出何时需要保持对象,何时需要自动释放对象,何时需要释放对象。

强变量

通常,所有对象的指针变量都是强变量。也就是说,将对象的引用赋给变量使对象自动保持。然后,旧对象的引用会在赋值前被释放。最终,强变量默认会被初始化为零。无论它是实例变量、局部变量还是全局变量,这都成立。

例如

Fraction *f1 = [[Fraction alloc] init];

Fraction *f2 = [[Fraction alloc] init];

f2 = f1;

当不使用ARC时,f2=f1将导致f2引用的对象丢失引用,从而产生内存泄漏。

当使用ARC时,f1和f2都是强变量。前面的赋值会是这样的:(下面的过程由编译器自动处理)
[f1 retain]; //保留新的值
[f2 release]; //释放旧值
f2 = f1; //复制引用

当然,你并不会看到是如何动作的,因为编译器都帮你做了,你只要编写赋值语句就可以了。

因为所有的对象变量默认都是强变量的,所以不需要先声明。但你仍然可以为变量使用关键字__strong:

__strong Fraction *f1;

值得注意的是,属性默认不是strong,其默认的特性是unsafe_unretained(相当于assign)。你需要这样为属性声明strong特性:

@property (strong, nonatomic) NSMutableArray *birdNames;

编译器会保证在事件循环中通过对赋值执行保持操作强属性能够存活下来。带有unsafe_unretained(相当于assign)或weak的属性不会执行这些操作。

弱变量

      当你声明一个弱变量时,系统会追踪赋值给这个变量的引用。当引用的对象释放时,弱变量会被自动设置为nil。给nil对象发送消息不会有反应,从而有效避免了崩溃。

可以使用__weak关键字声明一个弱变量:
__weak UIView *parentView;

或者为属性指定weak特性:
@property (weak, nonatomic) UIView *parentView;

       弱变量能够和代理(delegate)很好的协作。创建一个代理的弱变更引用,如果代理对象被销毁,变量就会被清零。ARC出现之前,这能够为程序员避免一些令人头痛的各种系统崩溃。

       需要注意的是,在iOS4和Mac OS X v10.6中不支持弱变量。在这种情况下,你仍然可以为属性使用unsafe_unretained(或assign)特性,或者将变量声明为__unsafe_unretained。然而,当引用的对象被销毁时,变量不再被清零。

@autoreleasepool块

      被@autoreleasepool指令围住的语句块定义了自动释放池的上下文。任何在这个上下文中创建的对象都是自动释放的,在自动释放池块结束的时候销毁这些对象(除非编译器在自动释放块结束后还需要保证这个对象的存在)。

autoreleasepool对于应用程序使用或者不使用ARC进行编译的情形均适用。

for (i = 0; i < n; ++i) {
   @autoreleasepool {
       ... //临时对象
   }
}

方法名和非ARC编译代码

      ARC与未使用ARC编译的代码一起运行。例如,需要链接到旧的框架。只要非ARC代码与标准的Cocoa命名规则一致,都会运行良好。当ARC遇到方法调用时,会检查方法名,如果名字以alloc、new、copy、mutableCopy或init这些词开头,它会假定方法返回对象的所有者给方法的调用者。

      这里所讨论的“词”使用了驼峰拼写法(camelCase),即名字中每个新词的第一个字母都以大写字母开头。编译器会假定具有allocFraction、newAddressCard和initWithWidth:addHeight:名字的方法都返回对象的拥有者,而newlyWeds、copycat和initials则不会。这些在ARC中都会自动处理,不需要操心,除非你使用的方法不符合标准的命名规则。在这种情况下,根据方法的名字,需要使用到这种方式隐性通知编译器该方法会返回对象的拥有者。

      注意,如果你试图合成属性,而属性的名字是以第一段提到的特殊词开头,编译器会提示一些错误。

标签: Objective-C

Powered by emlog  蜀ICP备18021003号   sitemap

川公网安备 51019002001593号