ARC

ARC以前的生活

reference counting

在ARC以前,是手动内存管理,但不论是自动的,还是手动的内存管理,reference counting始终是内存管理的一个核心内容。
用办公室的照明管理来类比说明一下reference counting的原理。

按以下规则来安排照明灯的管理

  1. 当某人进入办公室时,办公室是空,则他负责打开灯
  2. 之后有人进入办公室,照明灯继续使用
  3. 当某人离开办公室,他就不再需要照明灯了
  4. 当最后一个人离开了,他关掉照明灯

我们量化的来管理照明灯系统,使用counter来计数

  1. 当某人进入办公室时,办公室是空,counter +1,它由0变成了1,所以打开灯
  2. 当另外的人进来,counter +1,它由1变成2
  3. 当某人离开,counter -1,它由2变成1
  4. 当最后一个人离开,counter变成了0,则关掉灯

Drawing

reference counting的原理跟照明灯的管理是一样的

  1. 打开灯 == 创建(alloc/new/copy/mutableCopy group)一个Objective-C对象并使用它 reference counting = 1
  2. 又进来一个人使用灯 == 取得(retain)Objective-C对象所有权 reference counting = 2
  3. 有一个人离开,不再使用灯 == 放弃(release)Objective-C对象所有权 reference counting = 1
  4. 最后一个人离开,关掉灯 == 丢弃(dealloc)Objective-C对象 reference counting = 0

对于新创建的对象,方法名以alloc/new/copy/mutableCopy group开头的,才拥有这个对象,才有责任负责释放它。

实现对象的 dealloc

NSObject 类定义了一个名为 dealloc 的方法。这个方法在对象无主(没有所有者)的情况下, 当内存回收的时候会由系统自动调用。dealloc 方法的作用就是释放对象的内存,并弃掉它持有的任何资源—-以及它对其他对象的所有权。

- (void)dealloc{
    [_firstName release];
    [super dealloc];
}

手动管理内存的限定符

  • assign 对于NSInteger、CGPoint、C数据类型等,这些直接在栈上开辟的内存,无需管理他们内存计数器,使用assign比较合适
  • retain 对于在堆中开辟的内存,我们需要维护内存的计数器。
  • copy 如果指针A和指针B不想相互牵扯,A管理A的内存,B管理B的内存,copy正是为这个而生。

Autorelease

Autorelease从名字来看,你也许会认为它类似于ARC,但其实不是这样的,它更类似于C语言的局部变量。对于Autorelease,你可以像局部变量一样使用对象,这意味着当执行离开代码块,对象将自动调用release方法。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];

在以上代码的最后,[pool drain]将执行[obj release]。 Cocoa 希望程序中长期存在一个 autorelease 池。如果池不存在,autorelease 的对象就无从 release 了,从而造成内存泄露。当程序中没有 autorelease 池,你的程序还给对象发送 autorelease 消息,这是 Cocoa 会发出一个错误日志。AppKit 和 UIKit 框架自动在每个消息循环的开始都创建一个池(比如鼠标按下事件、触摸事件)并在结尾处销毁这个池。正因为如此,你实际上不需要创建 autorelease 池,甚至不需要知道创建 autorelease 池的代码如何写。下面三种情形下,你却应该使用你自己的 autorelease 池:

  1. 如果你写的程序,不是基于 UI Framwork。例如你写的是一个基于命令行的程序。
  2. 如果你程序中的一个循环,在循环体中创建了大量的临时对象。你可以在循环体内部新建一个 autorelease 池,并在一次循环结束时销毁这些临时对象。这样可以减少你的程序对内存的占用峰值。
  3. 如果你发起了一个 secondary 线程(main 线程之外的线程)。这时你“必须”在线程的最初执行代码中创建 autorelease 池,否则你的程序就内存泄露了。

Autorelease 池常常被称为“嵌套”的,但实际上也可以把这些嵌套的池理解为在一个栈中。当一个对象收到了 autorelease 消息时,又或者这个对象 作为 addObject:方法的参数传递的时候,这个对象被放到了当时这个栈中最顶端的那个池中。

ARC

在工程中使用ARC非常简单:只需要像往常那样编写代码,只不过永远不写retain,releaseautorelease三个关键字就好。 ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是在代码编译时为你自动在合适的位置插入releaseautorelease,就如同之前手动内存管理时你所做的那样。因此,至少在效率上ARC机制是不会比手动内存管理弱的,而因为可以在最合适的地方完成reference counting的维护,以及部分优化,使用ARC甚至能比MRC取得更高的运行效率。

ARC下有以下4个限定符

  • strong 跟retain一样,在ARC下,如果对象没有任何strong指针指向,那么就将被销毁
  • weak weak指针指向的对象reference counting不会增加,对象无strong指针指向时,对象会被销毁,在ARC机制作用下,所有指向这个对象的weak指针将被置为nil。delegate、outlet多使用weak
  • unsafe_unretained 这就是原来的assign。当需要支持iOS4时需要用到这个关键字
  • autoreleasing 这个就等于手动内存管理的Autorelease

上文Autorelease池的代码在ARC下就转换为:

@autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
}

桥接

ARC只适用于NSObject,底层对象依旧需要进行手动内存管理。在ARC中,编译器需要知道这些底层对象指针应该由谁来负责释放,这时就要进行桥接处理。

  • __bridge 只做类型转换,不改变对象所有权,是我们最常用的转换符。
  • __bridge_retained 将Objective-C对象转换为Core Foundation对象,把对象所有权桥接给Core Foundation对象,同时剥夺ARC的管理权,后续需要开发者使用CFRelease或者相关方法手动来释放对象。
  • __bridge_transfer 将非Objective-C对象转换为Objective-C对象,同时将对象的管理权交给ARC,开发者无需手动管理内存。

从OC转CF,ARC管理内存

NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge CFStringRef)aNSString;
(void)aNSString

从CF转OC,需要开发者手动释放,不归ARC管(void* id的转换)

CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);  
NSString *aNSString = (__bridge NSString *)aCFString;    
(void)aNSString;    
CFRelease(aCFString); 
ios

Comments