关于ARC技术,最重要的还是下面这一点:
++在LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者是release代码。++
OC中的内存管理,也就是引用计数。可以用开关灯房间的案例来说明:
假设办公室照明设备只有一个。上班的人进入办公室需要照明,所以把灯打开。而队对于下班离开办公室的人来说,已经不需要照明了,所以把等关掉。若是很多上下班,每个人都开灯或者关灯,那么办公室的情况又将如何呢?最早下班的人关灯,那么办公室岂不是一片黑暗。
解决这个问题的办法是使办公室还有至少一个人的情况下保持开灯状态,无人的时候保持关状态。
1. 最早进入办公室开灯
2. 之后进入办公室的人,需要照明
3. 下班离开办公室的人,不需要照明
4. 最后离开办公室的人关灯(此时已无人需要照明)
根据计数功能来计算 ”需要照明的人数”
在OC中,”对象” 相当于办公室的照明设备。
对象操作 | Objective-C方法 |
---|---|
生成并持有对象 | alloc/new/copy/muetableCopy等方法 |
持有对象 | retain 方法 |
释放对象 | release 方法 |
废弃对象 | dealloc 方法 |
上面出现了很多 “自己” 一词。本文所说的 “自己” 固然对应上面提到的 “对象使用环境”,但将之理解为编程人员 “自身” 也是没错的。
下面写出了自己生成并持有对象,我们使用alloc方法:
//自己生成并持有对象
id obj = [[NSObject alloc] init];
copy方法利用基于NSCopying方法约定,由各类实现copyWithZone:方法生成并持有对象的副本。与Copy方法类似,mutableCopy方法利用基于NSMutableCopying方法约定,由各类实现mutableCopyWhitZone:方法生成持有对象的副本。
这些方法生成的对象,虽然是对象的副本,但同alloc、new方法一样,在 “自己生成并持有对象” 这点上没有改变。
//取得非自己生成并持有的对象
id obj = [NSMutableArray array];
上述源码中,NSMutableArray类对象被赋给变量obj,但变量obj自己并不持有改对象。使用retain方法可以持有对象。
//持有对象
[obj retainl];
自己持有的对象,一旦不再需要,持有者有义务释放该对象。释放使用release方法。
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//释放
[obj release];
用 alloc/new/copy/mutableCopy 方法生成并持有的对象,或者用 retain 方法持有的对象,一旦不再需要,务必要用 release 方法进行释放。
如果要用某个方法生成对象,并将其返还给该方法的调用方,那么它的源码又是怎么样的。
+ (NSObject *) allocObject {
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
retain obj;
}
原封不动地返回用 alloc 方法生成并持有对象,就能让调用方也持有该对象。
// 取得非自己生成并持有的对象???
id obj1 = [NSObject allocObject];
allocObject与用alloc方法生成并持有对象的情况完全相同,所有使用allocObject方法也就意味着 “自己生成并持有对象”
那么,调用 [NSMutableArray array] 方法使取得的对象存在,但是自己又不持有对象,又是怎么实现的呢?
- (id)object {
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//使对象在超出指定的生成范围时能够自动并正确地的释放
[obj autorelease];
//返回对象
return obj;
}
上例中,我们使用了 autorelease 方法。用该方法,可以使取得的对象存在,但是自己不持有对象。
这样的好处就是,我们可以不用去手动去管理通过此方法 生成的对象 的释放。
autorelease 提供这样的功能,是对象在超出指定的生成范围时能够自动并正确地释放(调用release方法)
使用NSMutableArray类的array类方法等可以取得谁都不持有的对象,这些方法都是通过 autorelease 而实现的。此外 ++根据命名规则,这些取得谁都不持有的对象的方法名不能以 alloc/new/copy/mutableCopy 开头,这点需要注意。++
对于用 alloc/new/copy/mutableCopy 方法生成并持有的对象,或者是用 retain 方法持有的对象,由于持有者都是自己,所以在不需要该对象时需要将其释放。
而由此以外所得到的对象绝对不能释放。倘若在应用程序中释放了非自己所持有的对象会造成崩溃。
例如自己生成并持有对象后,在释放完后,再次释放。(重复调用release);
或者是在 “取得对象的存在,但是自己不持有对象” 时释放。
都会到导致应用程序崩溃!因此绝对不要释放非自己持有的对象!
以上五项内容,就是 “引用计数式内存管理” 的思考方式。
我们来看看 GUNstep 源代码中 NSObject 类的的 alloc 类方法。
id obj = [NSObject alloc];
上述调用 NSObject 类的 alloc 类方法在 NSObjecr.m 源代码中的实现如下。
+ (id) alloc
{
return [self allocWithZone: NSDefaultMallocZone()];
}
+ (id) allocWithZone:(NSZone *)z
{
return NSAllocateObject(self,0,z);
}
通过 allocWithZone: 类方法调用 N\NSAllocateObject 函数分配对象。下面我们查看 NSAllocateObject 函数.
struct obj_layout {
NSUInterger retained;
}
inline id NSAllocateObject (Class aClass, NSUInterger extraBytes, NSZone *zone) {
int size = 计算容纳对象所需内存大小;
id new = NSZoneMalloc(zone, size);
memset(new, 0, size);
new = (id)&((struct obj_layout *) new)[1];
}
NSAllocateObject 函数通过调用 NSZoneMalloc 函数来分配存放对象所需的内存空间,之后将改内存空间置0,最后返回作为对象而使用的指针。
以下是去掉 NSZone 后简化了源代码:
struct obj_layout {
NSUInterger retained;
}
+ (id) alloc {
int size = sizeof(struct obj_layout) + 对象大小;
struct obj_layout *p = (struct obj_layout *)calloc(1, size);
return (id)(p+1);
}
alloc 类方法用 struct obj_layout 中的 retained 整数来保存引用计数,并将其写入对象内存头部,该对象内存全部置0后返回。
以下用图来展示有关 GUNstep 的实现,alloc类方法返回对象,如图
对象的引用计数可通过 retainCount 实例方法来取得。
id obj = [NSObject alloc];
NSLog(@"retainCount=%d", [obj retainCount]); // => 1
执行alloc后对象的 retainCount 是 “1”。下面通过 GUNstep 的源代码来确认。
- (NSUInteger)retainCount {
return NSExtraRefCount(self) + 1;
}
inline NSUInterger NSExtraRefCount(id anObject) {
return ((struct obj_layout *) anObject)[-1].retained;
}
由对象寻址找到对象内存头部,从而访问其中的retained变量。如图:
autorelease 就是自动释放。这看上去很像ARC,但实际上它更类似于C语言中的自动变量(局部变量)的特性。
{
int a;
}
/*
因为超出变量作用域,自动变量 "int a" 被废弃,不可再访问
*/
autorelease 会像 C 语言的自动变量那样来对待对象实例。当超出其作用域(相当于变量作用域)时,对象实例的 release 实例方法被调用。另外,同 C 语言的自动变量不同的是,编程人员可以设定变量的作用域。
autorelease 的具体使用方法如下:
NSAutoreleasePool 对象的声明周期相当于 C 语言变量的作用域。对于所有调用过 autorelease 实例方法的对象,在废弃 NSAutoreleasePool 对象时,都将调用 release 实例方法。如上图。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
“[pool drain]” 等同于 “[obj release]”。
在 Cocoa 框架中,相当于程序主循环的 NSRunLoop 或者在其他程序可运行的地方,对 NSAutoreleasePool 对象进行生成、持有和废弃处理。因此,应用程序开发者不一定非得使用 NSAutoreleasePool 对象来进行开发工作。
意思就是我们不一定需要去管理 NSAutoreleasePool。
尽管如此,但在大量产生 autorelease 的对象时,只要不废弃 NSAutoreleasePool 对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象。
典型的例子:读入大量图像的同时改变尺寸。图像文件读入到 NSData 对象,并从中生成 UIImage 对象,改变该对象尺寸后生成新的 UIImage 对象。这种情况下,就会大量生成 autorelease 对象。
for (int i = 0; i < 图像数; ++i){
/*
读入图像
大量产生 autorelease 对象
由于没有废弃 NSAutoreleasePool 对象
最终导致内存不足
*/
}
在此情况下,有必要在适当的地方生成,持有或者废弃 NSAutoreleasePool 对象。
for (int i = 0; i < 图像数; ++i){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/*
读入图像
大量产生 autorelease 对象
*/
[pool drain];
}
另外,Cocoa 框架中也有很多类方法用于返回 autorelease 的对象。比如 NSMutableArray 类的 arrayWithCapacity 类方法。
id array = [NSMutableArray arrayWithCapacity:1];
此源代码等同于以下源代码
id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
[obj autorelease];
此源代码调用 NSObject 类的 autorelease 实例方法。
GUNstep实现:
- (id) autorelease {
[NSAutoreleasePool addObject:self];
}
autorelease 实例方法的本质就是调用 NSAutoreleasePool 对象的 addObject 类方法。
+ (void) addObject:(id)anObj {
NSAutoreleasePool *pool = 取得正在使用的 NSAutoreleasePool 对象;
if (pool != nil) {
[pool addObject: anObjc];
}else{
NSLog(@"NSAutoreleasePool 对象非存在状态下调用 autorelease")
}
}
addObject 类方法调用正在使用的 NSAutoreleasePool 对象的 addObject 实例方法。
如果嵌套或者持有 NSAutoreleasePool 对象,理所当然会使用最内侧的对象。
- (void) drain {
[self dealloc];
}
- (void) dellloc {
[self emptyPool];
[array release];
}
- (void) emptyPool {
for (id obj in array){
[obj release];
}
}
虽然调用了好几个方法,但是可以确定对于数组中的所以对象都调用了 release 实例方法。