"我一直想拥有和控制我们所做的主要技术。"
在开始介绍ARC之前,先介绍应用内存的有关概念。
栈 和 堆
栈(The Stack)
当程序执行某个方法(函数)时,会从内存中名为栈(stack)的区域分配一块内存空间,这块内存空间也叫帧(frame)。帧负责保存程序在方法内声明的变量的值。在方法内声明的变量称为局部变量(local variable)。
当某个应用启动并运行main函数时,它的帧会被保存在栈的底部。当main函数调用另一个方法(或函数)时,这个方法的帧会被压入栈的顶部。被调用的方法还可以再调用其他方法,以此类推,最终会在栈中形成一个塔状的帧序列。当被调用的方法(或函数)结束时,程序会将其帧从栈顶“弹出”并释放,出栈采取“先进后出”的方式。
堆(heap)
堆(heap)是指内存中的另一块区域,和栈是分开的。栈按照后进先出的规则保存一组帧,而堆则包含了大量的无序的活动对象,为了使用这些对象,需要通过指针变量来保存这些对象在内存中的地址。
指针变量与对象所有权
指针变量保存的是对象在内存中的地址,暗含了对所指对象的使用权。
当一个变量指向某个对象时,我们称该变量拥有(own)其所指向的对象。
由于设备可供应用支配的堆空间是有限的,所以,对于不再使用的对象,应该将它释放掉并回收该对象所占用的内存空间。当然,释放不宜过早,释放过早会导致产生空引用(或空指针)。因此,对于一个对象的拥有者个数应该做一个统计,这就是引用计数。
在Apple引入ARC之前,应用只能通过手动引用计数(manual reference counting)来管理内存。即在使用对象过程中,调用相应的方法对该对象的引用计数进行加(retain)或者减(release)。
自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术。
要使用ARC,需要满足以下条件:
1.使用Xcode4.2或以上版本
2.使用LLVM编译器3.0或以上版本
3.编译器选项中设置ARC有效
生活例子:办公室开关灯
- 最早进入办公室的人开灯。 count = 1
- 之后进入办公室的人,需要照明。 count = 2
- 下班离开办公室的人,不需要照明。 count = 1
- 最后离开办公室的人关灯。(此时已无人需要照明) count = 0
内存管理
思考方式:
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也能持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
表 1- 2 对象操作与Objective-C方法的对应
对象操作 | Objective-C方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy方法 |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc方法 |
属性的特性
- 多线程特性(atomic, nonatomic)
- 读/写特性(readwrite, readonly)
- 内存管理特性(strong, weak, copy, unsafe_unretained)
@property (nonatomic, readwrite, strong) NSMutableArray *students;
修饰符
- __strong 修饰符
- __weak 修饰符
- __unsafe_unretained 修饰符
- __autoreleasing 修饰符
__unsafe_unretained是不安全的所有权修饰符(相对弱引用而言)。尽管ARC式的内存管理是编译器的工作,但附有__unsafe_unretained 修饰符的变量不属于编译器的内存管理对象。
__unsafe_unretained类型的指针指向的对象被销毁时,指针不会自动设置为nil,而是成为空指针,因此赋值给附有__unsafe_unretained修饰符的对象在通过该变量使用时,如果没有确保其确实存在(可能已经被废弃,悬垂指针),那么应用程序可能会崩溃。
为什么需要使用附有__unsafe_unretained修饰符?
1.__unsafe_unretained是非对象属性的默认值;
2.在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_unretained修饰符来替代__weak修饰符。
强引用
只要指针变量指向了某个对象,那么相应对象就会多一个拥有者(即引用计数+1),这种指针特性称为强引用(stong reference)。
弱引用
程序也可以选择让指针变量不影响其所指向对象的拥有者个数(引用计数不变)。这种指针特性称为弱引用(weak reference)。
ARC规则:
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 须遵守内存管理的方法命名规则
- 不要显式调用dealloc
- 使用@autoreleasepool块替代NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结构体(struct/union)的成员
- 显示转换“id”和“void *”
@autoreleasepool
如果某个方法会返回一个新创建的对象,而方法名不包含alloc和init,那么通常会把该对象放入相应的自动释放池。
@autoreleasepool { Student *student = [Student someStudent]; }
为避免内存峰值,在不断地读入比较大的数据时,也可以用。
for (NSInteger i = 0; i < 10000; i++) { @autoreleasepool { NSData *data = [NSData dataWithContentsOfFile:@"path" options:NSDataReadingMappedAlways error:nil]; } }
区域(zone)
NSDefaultMallocZone
、NSZoneMalloc
等名称中包含的NSZone
是什么呢?它是为防止内存碎片化而引入的结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高了内存管理的效率。但是,如同苹果官方文档Programming With ARC Release Notes中所说,现在的运行时系统只是简单地忽略了区域的概念。运行时系统中的内存管理本身已极具效率,使用区域来管理内存反而会引起内存使用效率低下以及源代码复杂化问题。
对象型变量不能作为C语言结构体的成员
struct Data { NSMutableArray *array; }
会引发编译错误。
虽然是LLVM编译器3.0,但C语言的规约上没有方法来管理结构体成员的生存周期。以为ARC把内存管理的工作分配给编译器,所以编译器必须能够知道并管理对象的生存周期。例如C语言的自动变量(局部变量)是使用该变量的作用域管理对象。但是对于C语言的结构体成员来说,这在标准上就是不可实现的。
要把对象型变量加入到结构体成员中时,可强制转换为void * 或者是附加__unsafe_unretained修饰符。
** 显示转换“id”和“void ”*
/* ARC无效 */
id obj = [[NSObject alloc] init];
void *p = obj;
id q = p;
/* ARC有效 */
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id q = (__bridge id)p;
ARC有效时,通过“__bridge”转换,id和void *就能够相互转换。但是转换为void *的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。
__bridge转换中还有两种,分别是“__bridge_retained”和“__bridge_transfer”。
__bridge_retained转换可使要转换赋值的变量也持有所赋值的对象。
__bridge_transfer转换提供相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。
这些转换多数发生在Objective-C对象与Core Foundation对象之间的相互变换中。
Objective-C对象与Core Foundation对象
Core Foundation对象主要使用在用C语言编写的Core Foundation框架中,并使用引用计数的对象。在ARC无效时,Core Foundation框架中的retain/ release分别是CFRetain/CFRelease。
Core Foundation对象与Objective-C对象的区别很小,不同之处只在于是由哪一个框架(Core Foundation或者Foundation)所生成的。无论是由哪种框架生成的对象,一旦生成之后,便能在不同的框架中使用。Foundation框架的API生成并持有的对象可以用Core Foundation框架的API释放。反之亦然。
因为二者区别不大,所以,在ARC无效时,只用简单的C语言的转换也能实现互换。 另外这种转换不需要使用额外的CPU资源,因此也被称为“免费桥”(Toll-free Bridge)。
网友评论