iOS的内存管理

时间:2021-10-22 21:52:07

  在Objective-C 这种面向对象的语言里,内存管理是个重要的概念。要想用一门语言写出内存使用效率高而且又没有bug的代码,就得掌握其内存管理模型的种种细节。

  一旦理解了这些规则,你就会发现,其实Objective-C 的内存管理没那么复杂,而且有了"自动引用计数"(Automatic Reference Counting,ARC)之后,就变得更为简单了。ARC几乎把所有内存管理事宜都交给编译器来决定,开发者只需关注于业务逻辑。

引用计数

  Objective-C 中的内存管理,也就是引用计数。可以用开关房间的灯为例来说明引用计数机制。

   假设办公室的照明设备只有一个。上班进入办公室的人需要照明。所以要把灯打开。而对于下班离开办公室的人来说,已经不需要照明了,所以需要把灯关掉。若 是很多人上下班,每个人都开灯或是关灯,那么办公室的情况又将如何呢?最早下班离开的人如果关了灯,那就会让办公室还没走的所有人的将处于一片黑暗之中。

  解决这一问题的办法是使办公室在还有至少1人的情况下保持开灯状态,而在无人时保持关灯状态。

  (1)最早进入办公室的人开灯。

  (2)之后进入办公室的人,需要照明。

  (3)下班离开办公室的人,不需要照明。

  (4)最后离开办公室的人关灯(此时已无人需要照明)。

  为判断是否还有人在办公室里,这里导入计数功能来计算"需要照明的人数"。下面让我们来看一看这一功能是如何运作的。

  (1)第一个人进入办公室,"需要照明的人数"加1。计数值从0变成了1,因此要开灯。

  (2)第二个人进入办公室,"需要照明的人数"加1。计数值从1变成了2。

  (3)每当有人下班离开办公室时,"需要照明的人数"就减1。如计数值从2变成了1。

  (4)最后一个人下班离开办公室时,"需要照明的人数"就减1。计数值从1变成了0,因此要关灯。

  这样就能在不需要照明的时候保持关灯状态。办公室中仅有的照明设备也得到了很好的管理。

  在 Objective-C 中,"对象"相当于办公室的照明设备。在现实世界中办公室的照明设备只有一个,但在Objective-C的世界里,虽然计算机资源有限,但一台计算机可以同时处理好几个对象。

  此外,"对象的使用环境"相当于上班进入办公室的人。虽然这里的"环境"有时也指在运行中的程序代码、变量、变量作用域、对象等,但在概念上就是使用对象的环境。上班进入办公室的人对办公室照明设备发出的动作,与 Objective-C 的对应关系如下:

对照明设备所做的动作 对OC对象所做的动作
开灯 生成对象
需要照明 持有对象
不需要照明 释放对象
关灯 销毁对象

  使用引用计数功能计算需要照明的人数,使办公室的照明得到了很好的管理。同样,使用引用计数功能,对象也能得到很好的管理,这就是 Objective-C 的内存管理。

内存管理的思考方式

  首先来学习引用计数式内存管理的思考方式。看到"引用计数"这个名称,我们便会不自觉地联想到"某处有某物多少多少"而将注意力放在计数上。但其实,更加客观、正确的思考方式是:

  •   自己生成的对象,自己持有。
  • 非自己生成的对象,自己也能持有。
  • 不再需要自己持有的对象时释放。
  • 非自己持有的对象无法释放。

  引用计数式的内存管理的思考方式仅此而已。按照这个思路,完全不必考虑引用计数。

  上文出现了"生成"、"持有"、"释放"三个词。而在Objective-C内存管理中还要加上"废弃"一词。各个词表示的 Objective-C方法如表

对象操作 Objective-C方法
生成并持有对象 alloc/new/copy/mutableCopy等方法
持有对象 retain方法
释放对象 release方法
销毁对象 dealloc方法

  这些有关Objective-C内存管理的方法,实际上不包括在该语言中,而是包含在Cocoa框架中用于OSX、iOS应用开发。

ARC规则

  "引用计数式内存管理"的本质部分在ARC中并没有改变。就像"自动引用计数"这个名称表示的那样,ARC只是自动地帮助我们处理"引用计数"的相关部分。

所有权修饰符

  Objective-C编程为了处理对象,可将变量类型定义为id类型或各种对象类型。

  所谓对象类型就是指向NSObject这样的Objective-C类的指针,例如"NSObject*"。id类型用于隐藏对象类型的类名部分,相当于C语言中常用的"void*"。

  ARC有效时,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符。所有权修饰符一共有4种。

  •   __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

__strong修饰符

  __strong修饰符是id类型和对象类型默认的所有权修饰符。也就是说,以下源代码中的id变量,实际上被附加了所有权修饰符。

  

    id obj = [[NSObject alloc]init];

  id和对象类型在没有明确指明所有权修饰符时,默认为__strong修饰符。上面的源代码与以下相同。

    id __strong obj = [[NSObject alloc]init];

  该源代码在ARC无效时又该如何表述呢?

  

    /*    ARC无效    */
id obj = [[NSObject alloc]init];

  该源代码一看则明,目前在表面上并没有任何变化。再看看下面的代码。

{
id __strong obj = [[NSObject alloc]init];
}

  此源代码明确指定了C语言的变量的作用域。ARC无效时,该源代码可记述如下:

/*    ARC无效    */
{
id obj = [[NSObject alloc]init];
[obj release];
}

  为了释放生成并持有的对象,增加了调用release方法的代码。该源代码进行的动作同先前ARC有效时的动作完全一样。

  如此源代码所示,附有__strong修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。

  如"__strong"这个名称所示,__strong修饰符表示对对象的"强引用"。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

  下面关注一下源代码中关于对象的所有者的部分。

{
id __strong obj = [[NSObject alloc]init];
}

  此源代码就是之前自己生成并持有对象的源代码,该对象的所有者如下:

{
/*
* 自己生成并持有对象
*/
id __strong obj = [[NSObject alloc]init];
/*
* 因为变量obj为强引用
* 所以自己持有对象
*/
}
/*
* 因为变量obj超出其作用域,强引用失效,
* 所以自动地释放自己持有的对象。
* 对象的所有者不存在,因此废弃改对象。
*/

  此外,对象的所有者和对象的生命周期是明确的。那么在取得非自己生成并持有的对象时又会如何呢?

  

{
id __strong obj = [NSMutableArray array];
}

  在NSMutableArray类的array类方法的源代码中取得非自己生成并持有的对象,具体如下:

{
/*
* 取得非自己生成并持有的对象
*/ id __strong obj = [NSMutableArray array]; /*
* 因为变量obj为强引用,
* 所以自己持有对象
*/
}
/*
* 因为变量obj超出其作用域,强引用失效,
* 所以自动地释放自己持有的对象
*/

  在这里对象的所有者和对象的生存周期也是明确的。

{
/*
* 自己生成并持有的对象
*/ id __strong obj = [[NSObject alloc]init]; /*
* 因为变量obj为强引用,
* 所以自己持有对象
*/
}
/*
* 因为变量obj超出其作用域,强引用失效,
* 所以自动地释放自己持有的对象。
* 对象的所有者不存在,因此废弃该对象。
*/

  当然,附有__strong修饰符的变量之间可以相互赋值。

  

    id __strong obj0 = [[NSObject alloc]init];

    id __strong obj1 = [[NSObject alloc]init];

    id __strong obj2 = nil;

    obj0 = obj1;

    obj2 = obj0;

    obj1 = nil;

    obj0 = nil;

    obj2 = nil;

  下面来看一下生成并持有对象的强引用。

    id __strong obj0 = [[NSObject alloc]init];  /*  对象A */

    /*
* obj0 持有对象A的强引用
*/ id __strong obj1 = [[NSObject alloc]init];/* 对象B */ /*
* obj1 持有对象的B强引用
*/ id __strong obj2 = nil; /*
* obj2 不持有任何对象
*/ obj0 = obj1; /*
* obj0 持有由 obj1 赋值的对象B的强引用
* 因为 obj0 被赋值,所以原先持有的对对象A的强引用失效。
* 对象A的持有者不存在,因此废弃对象A。
*
* 此时,持有对象B的强引用的变量为
* obj0 和 obj1
*/ obj2 = obj0; /*
* obj2 持有由 obj0 赋值的对象B的强引用
*
* 此时,持有对象B的强引用的变量为
* obj0 , obj1 和 obj2
*/ obj1 = nil; /*
* 因为 nil 被赋予了 obj1 , 所以对对象B的强引用失效。
*
* 此时,持有对象B的强引用的变量为
* obj0 和 obj2
*/ obj0 = nil; /*
* 因为 nil 被赋予了 obj0 , 所以对对象B的强引用失效。
*
* 此时,持有对象B的强引用的变量为
* obj2
*/ obj2 = nil; /*
* 因为 nil 被赋予了 obj2 , 所以对对象B的强引用失效。
* 对象B的所有者不存在,因此废弃对象B。
*/

  通过上面这些不难发现,__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确地管理器对象的所有者。

  当然,即便是 Objective-C类成员变量,也可以在方法参数上,使用附有__strong修饰符的变量。

@interface Test : NSObject
{
id __strong _obj;
} - (void)setObject:(id __strong)obj;
@end @implementation Test
- (id)init
{
self = [super init];
return self;
} - (void)setObject:(id __strong)obj
{
_obj = obj;
}
@end

  接着试着使用该类。

{
id __strong test = [[Test alloc]init];
[test setObject:[NSObject alloc]init];
}

  该例中生成并持有对象的状态记录如下:

{
id __strong test = [Test alloc]init];
/*
* test 持有Test对象的强引用
*/ [test setObject:[NSObject alloc]init]; /*
* Test 对象的_obj成员
* 持有NSObject对象的强引用
*/
}
/*
* 因为test变量超出其作用域,强引用失效,
* 所以自动释放Test对象
* Test对象的所有者不存在,因此废弃该对象。
*
* 废弃Test对象的同时,
* Test对象的 _obj成员也被废弃,
* NSObject 对象的强引用失效
* 自动释放NSObject对象
* NSObject对象的所有者不存在,因此废弃该对象。
*/

  像这样,无需额外工作便可以使用与类成员变量以及方法参数中。

  另外,__strong修饰符通后面要讲的__weak修饰符和__autoreleasing修饰符一起,可以保证将附有这些修饰符的自动变量初始化为nil。

  

    id __strong obj0;
id __weak obj1;
id __autoreleasing obj2;

  以下源代码与上相同。

    id __strong obj0 = nil;
id __weak obj1 = nil;
id __autoreleasing obj2 = nil;

  正如苹果宣称的那样,通过__strong修饰符,不必再次键入retain或者release,完美的满足了"引用计数式内存管理的思考方式":

  • 自己生成的对象,自己所持有
  • 非自己生成的对象,自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放

   前两项"自己生成的对象,自己持有"和""非自己生成的对象,自己也能持有"只需通过对带__strong修饰符的变量赋值便可达成。通过废弃带 __strong修饰符的变量(变量作用域结束或是成员变量所属对象废弃)或者对变量赋值,都可以做到"不在需要自己持有的对象时释放"。最后一项"非自 己持有的对象无法释放",由于不必再次键入release,所以原本就不会执行。这些都满足于引用计数式内存管理的思考方式。

  因为id类型和对象类型的所有权修饰符默认为__strong修饰符,所以不需要写上"__strong"。使ARC有效及简单的编程遵循了Objective-C内存管理的思考方式。

__weak修饰符

  看起来好通过__strong修饰符编译器就能够完美的进行内存管理。但是遗憾的是,仅通过__strong修饰符是不能解决有些重大问题的。

  这里提到的重大问题就是引用计数式内存管理中必然会发生的"循环引用"的问题。

  例如,前面出现的带有__strong修饰符的成员变量在持有对象时,很容易发生循环引用问题。

@interface Test : NSObject
{
id __strong _obj;
} - (void)setObject:(id __strong)obj;
@end @implementation Test
- (id)init
{
self = [super init];
return self;
} - (void)setObject:(id __strong)obj
{
_obj = obj;
}
@end

  以下为循环引用。

{
id test0 = [[Test alloc] init];
id test1 = [[Test alloc] init];
[test0 setObject:test1];
[test1 setObject:test0];
}

  为便于理解,下面写出了生成并持有对象的状态。

{

    id test0 = [[Test alloc] init];  /*  对象A */

    /*
* test0 持有对象A的强引用
*/ id test1 = [[Test alloc] init];/* 对象B */ /*
* test1 持有对象B的强引用
*/ [test0 setObject:test1]; /*
* Test对象A的_obj成员变量持有Test对象B的强引用
*
* 此时,持有Test对象B的强引用变量为
* Test对象A的_obj和test1
*/ [test1 setObject:test0]; /*
* Test对象B的_obj成员变量持有Test对象A的强引用
*
* 此时,持有Test对象A的强引用变量为
* Test对象B的_obj和test0
*/
}
/*
* 因为test0变量超出其作用域,强引用失效,
* 所以自动释放Test对象A。
*
* 因为test1变量超出其作用域,强引用失效,
* 所以自动释放Test对象B。
*
* 此时,持有Test对象A的强引用的变量为
* Test对象B的_obj。
*
* 此时,持有Test对象B的强引用的变量为
* Test对象A的_obj。
*
* 发生内存泄漏。
*/

  循环引用容易发生内存泄漏。所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。

  此代码的本意是赋予变量test0的对象A和赋予标量test1的对象B在超出期变量作用域时被释放,即在对象不被任何变量持有的状态下予以废弃。但是,循环引用使得对象不能被再次废弃。

  向下面这种情况,虽然只有一个对象,但在该对象持有其自身时,也会发生循环引用。

    id test = [[Test alloc]init];
[test setObject:test];

  怎么样才能避免循环引用呢?看到__strong修饰符就会意识到了,既然有strong,就应该会有与之对应的weak。也就是说,__weak修饰符可以避免循环引用。

  __weak修饰符与__strong修饰符相反,提供弱引用。弱引用不能持有对象实例。我们来看下面的代码:

    id __weak obj = [[NSObject alloc]init];

  变量obj上附加了__weak修饰符。实际上如果编译以上代码,编译器会发出警告。

   此源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量obj。即变量obj持有对象的弱引用。因此,为了不以自己持有的状态来保存自己生 成并持有的对象,生成的对象会立即释放。编译器会发出警告。如果像下面这样,将对象赋值给附有__strong修饰符的变量之后再赋值给附有__weak 修饰符的变量,就不会发出警告了。

{
id __strong obj0 = [[NSObject alloc]init];
id __weak obj1 = obj0;
}

  下面确认对象的持有状况。

{
 /*
  * 自己生成并持有对象
  */

  id __strong obj0 = [[NSObject alloc]init];
  
  /*
  * 因为obj0变量为强引用
  * 所以自己持有对象
  */   id __weak obj1 = obj0;   /*
  * obj1变量持有生成对象的弱引用
  */
}
/*
* 因为obj0变量超出其作用域,强引用失效,
* 所以自动释放自己持有的对象
* 因为对象的所有者不存在,所以废弃该对象
*/

  因为带__weak修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。如果像下面这样将先前发生循环引用的类成员变量改成附有__weak修饰符的成员变量的话,该现象便可避免。

@interface Test : NSObject
{
id __weak _obj;
}
- (void)setObject:(id __strong)obj;
@end

  __weak修饰符还有一个优点。在持有某个对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)。如以下代码所示。

   id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc]init];
obj1 = obj0;
NSLog(@"A: %@",obj1);
}
NSLog(@"B: %@",obj1);

  此源代码执行结果如下:

    A: <NSObject: 0x753e180>
B: (null)

  下面我们来确认一下对象的持有情况,看看为什么得到这样的执行结果。

    id __weak obj1 = nil;
{
/*
* 自己生成并持有对象
*/ id __strong obj0 = [[NSObject alloc]init]; /*
* 因为obj0变量为强引用
* 所以自己持有对象
*/ obj1 = obj0; /*
* obj1变量持有对象的弱引用
*/ NSLog(@"A: %@",obj1); /*
* 输出obj1变量持有的弱引用的对象
*/
} /*
* 因为obj0变量超出其作用域,强引用失效
* 所以自动释放自己持有的对象
* 因为对象无持有者,所以废弃该对象
*
* 废弃对象的同时
* 持有该对象弱引用的obj1变量的弱引用失效,nil赋值给obj1。
*
*
*/ NSLog(@"B: %@",obj1);
/*
* 输出赋值给obj1变量中的nil
*/

  像这样,使用__weak,修饰符可避免循环引用。通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已被废弃。

__unsafe_unretained修饰符

  __unsafe_unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。尽管ARC式的内存管理是编程器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。这一点在使用时需要注意。

    id __unsafe_unretained obj = [[NSObject alloc]init];

  该源代码将自己生成并持有的对象赋值给附有__unsafe_unretained修饰符的变量中。虽然使用了unsafe变量,但编译器不会忽略,而是给与适当的警告。

  附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。到这里,__unsafe_unretained修饰符和__weak修饰符是一样的,下面我们来看看源代码的差异。

    id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc]init];
obj1 = obj0;
NSLog(@"A: %@",obj1);
} NSLog(@"B: %@",obj1);

  该源代码的执行结果为:

 A: <NSObject: 0x753e180>
B: <NSObject: 0x753e180>

  我们还像以前那样,通过确认对象的持有情况来理解发生了什么。

    id __unsafe_unretained obj1 = nil;
{
/*
* 自己生成并持有对象
*/ id __strong obj0 = [[NSObject alloc]init]; /*
* 因为obj0变量为强引用
* 所以自己持有对象
*/ obj1 = obj0; /*
* 虽然 obj0 变量赋值给 obj1
* 但是obj1 变量既不持有对象的强引用也不持有对象的弱引用
*/ NSLog(@"A: %@",obj1); /*
* 输出obj1变量表示的对象
*/
} /*
* 因为obj0变量超出其作用域,强引用失效
* 所以自动释放自己持有的对象
* 因为对象无持有者,所以废弃该对象。
*/ NSLog(@"B: %@",obj1);
/*
* 输出赋值给obj1变量表示的对象
*
* obj1变量表示的对象
* 已经被废弃(悬垂指针)!
* 错误访问!
*/

  也就是说,最后一行的NSLog只是碰巧正常运行而已。虽然访问了已经被废弃的对象,但应用程序在个别运行状态下才会崩溃。

  在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被复制的对象确实存在。

__autoreleasing修饰符

  ARC有效时不能使用autorelease方法,也不能使用NSAutoreleasePool类。这样一来,虽然autorelease无法直接使用,但实际上,ARC有效时autorelease功能是起作用的。

  ARC无效时会像下面这样来使用:

   /*  ARC无效   */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
id obj = [[NSObject alloc]init];
[obj autorelease];
[pool drain];

  ARC有效时,该源代码也能写成下面这样:

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

  指定"@autoreleasepool块"来替代"NSAutoreleasePool类对象生成、持有以及废弃"这一范围。

  另外ARC有效时,要通过将对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。对象赋值给附有__autoreleasing修饰符的变量等价于ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool中。

  也就是说可以理解为,在ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法。

  但是显示地附加__autoreleasing修饰符同显式的附加__strong修饰符一样罕见。

  取得非自己生成并持有的对象时,如同一下源代码,虽然可以使用alloc/new/copy/mutableCopy以外的方法来取得对象,但该对象已被注册到了autoreleasepool。这同在ARC无效时取得调用了autorelease方法的对象是一样的。这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool。另外init方法返回值的对象不注册到autoreleasepool。

   @autoreleasepool {
id __strong obj = [NSMutableArray array];
}

  我们再来看看该源代码中对象的所有状况。

 @autoreleasepool {
/*
* 取得非自己生成并持有的对象
*/ id __strong obj = [NSMutableArray array]; /*
* 因为变量obj为强引用
* 所以自己持有对象
*
* 并且该对象
* 由编译器判断其方法名
* 自动注册到autoreleasepool
*/
}
/*
* 因为变量obj超出其作用域,强引用失效,
* 所以自动释放自己持有的对象
*
* 同时随着@autoreleasepool块的结束
* 注册到autoreleasepool中的
* 所有对象被自动释放
*
* 因为对象的所有者不存在,所以废弃对象
*/

  像这样不使用__autoreleasing修饰符也能使对象注册到autoreleasepool。以下为取得非自己生成并持有对象时被调用方法的源代码示例。

+ (id)array
{
return [[NSMutableArray alloc]init];
}

  该源代码也没有使用__autoreleasing修饰符,可写成以下形式。

+ (id)array
{
id obj = [[NSMutableArray alloc]init]
return obj;
}

  因为没有显式指定所有权修饰符,所以id obj同附有__strong修饰符的id _strong obj是完全一样的。由于return使得对象变量超出期作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。

  以下为使用__weak修饰符的例子。虽然__weak修饰符是为了避免循环引用而使用的,但在访问附有__weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。

  

   id __weak obj1 = obj0;
NSLog(@"class = %@ ",[obj1 class]);

  以下源代码与此相同。

    id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@ ",[tmp class]);

  为什么在访问__weak修饰符的变量时必须要访问注册到autoreleasepool的对象呢?这是因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。因此,在使用附有__weak修饰符的变量时就必定要使用注册到autoreleasepool中的对象。

  最后一个非显式地使用__autoreleasing修饰符的例子,同前面讲述的id obj和id __strong obj完全一样。那么id的指针id *obj又如何呢?可以由id __strong obj的例子类推出id __strong *obj吗? 其实,推出来的是id __autoreleasing *obj。同样的,对象的指针NSObject **obj便成为了NSObject* __autoreleasing *obj。

  像这样,id的指针或对象的指针在没有显式的指定时会被附加上__autoreleasing修饰符。

  比如,为了得到详细的错误信息,经常会在方法的参数中传递NSError对象的指针,而不是函数返回值。Cocoa框架中,大多数方法也是使用这种方法,如NSString的stringWithContentsOfFile:encoding:error类方法等。使用该方式的源代码如下所示。

   NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];

  该方法的声明为:

- (BOOL) performOperationWithError:(NSError **)error;

  同前面讲述的一样,id的指针或对象的指针会默认附加上__autoreleasing修饰符,所以等同于以下源代码。

  

- (BOOL) performOperationWithError:(NSError * __autoreleasing*)error;

  参数中持有对象指针的方法,虽然为响应其执行结果,需要生成NSError类对象,但也必须符合内存管理的思考方式。

  作为alloc/new/copy/mutableCopy方法返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成并持有的对象。因此,使用附有__autoreleasing修饰符的变量作为对象取得参数,与出alloc/new/copy/mutableCopy外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。

  比如,performOperationWithError方法的源代码应该是下面这样:

- (BOOL) performOperationWithError:(NSError * __autoreleasing*)error{
/* 错误发生 */
*error = [[NSError alloc]initWithDomain:MyAppDomain code:errorCode userInfo:nil]; return NO; }

  因为声明为 NSError *__autoreleasing * 类型的error作为*error被赋值,所以能够返回注册到autoreleasepool中的对象。

  然而,下面的源代码会产生编译器错误。

  

    NSError *error = nil;
NSError **pError = &error;

  赋值给对象指针时,所有权修饰符必须一致。

  此时,对象指针必须附加__strong修饰符

 NSError *error = nil;
NSError * __strong *pError = &error;
/* 编译正常 */

  当然对于其他所有权修饰符也是一样。

 NSError  __weak *error = nil;
NSError *__weak *pError = &error;
/* 编译正常 */
NSError __unsafe_unretained *unsafeError = nil;
NSError *__unsafe_unretained *pUnsafeError = &unsafeError;
/* 编译正常 */

  前面的方法参数中使用了附有__autoreleasing修饰符的对象指针类型。

- (BOOL) performOperationWithError:(NSError * __autoreleasing*)error;

  然而调用方法却使用了附有__strong修饰符的对象指针类型。

    NSError __strong *error = nil;
BOOL result = [obj performOperationWithError:&error];  

  对象指针赋值时,其所有权修饰符必须一致,但为什么该源代码没有警告就顺利通过编译了呢?实际上,编译器自动地将该源代码转化成了下面这种形式。

  

     NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp]; 
error = tmp;

  当然也可以显式的指定方法参数中对象的指针类型的所有权修饰符。

- (BOOL) performOperationWithError:(NSError * __strong*)error;

  向该源代码声明的一样,对象不注册到autoreleasepool也能够传递。但是前面也说过,只有作为alloc/new/copy/mutableCopy方法的返回值而取得对象时,能够自己生成并持有对象。其他情况即为"取得非自己生成并持有的对象",这些务必牢记。为了在使用参数取得对象时,贯彻内存管理的思考方式,我们要将参数声明为附有__autoreleasing修饰符的对象指针类型。

  另外,虽然可以非显式的指定__autoreleasing修饰符,但在显式的指定__autorelesing修饰符时,必须注意对象变量要为自动变量(包括局部变量、函数以及方法参数)。

  下面,我们换个话题,详细了解下@autoreleasepool。如以下源代码所示,ARC无效时,可将NSAutoreleasepool对象嵌套使用。

  /*   ARC无效     */
NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc]init];
NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc]init];
NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc]init];
id obj = [[NSObject alloc]init];
[obj autorelease];
[pool2 drain];
[pool1 drain];
[pool0 drain];

  同样的,@autoreleasepool块也可以嵌套使用。

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

  比如 ,在iOS应用程序模板中,向下面的main函数一样,@autoreleasepool块包含了全部程序。

int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

  NSRunLoop等实现不论ARC有效还是无效,均能够随时释放注册到autoreleasepool中的对象。

  另外,即使ARC无效时,@autoreleasepool块也能够使用,如以下所示:

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

  因为autoreleasepool范围以块级源代码表示,提高了程序的可读性,所以无论ARC 是否有效都推荐使用@autoreleasepool块,另外调试用的非公开函数_obj_autoreleasePoolPrint()都可使用,利用这一函数可有效地帮助我们调试注册到autoreleasepool上的对象。  

规则

  在ARC有效的情况下,编译源代码,必须遵循一定的规则。下面就是具体的ARC规则:

  • 不能使用retain/release/retain/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 须遵守内存管理的方法命名规则
  • 不要显示调用dealloc
  • 使用@autoreleasepool块替代NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象型变量不能作为C语言结构体(struct/union)的成员
  • 通过"__bridge" 显式转换"id"和"void*"