在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*"

iOS的内存管理的更多相关文章

  1. 理解 iOS 的内存管理

    远古时代的故事 那些经历过手工管理内存(MRC)时代的人们,一定对 iOS 开发中的内存管理记忆犹新.那个时候大约是 2010 年,国内 iOS 开发刚刚兴起,tinyfool 大叔的大名已经如雷贯耳 ...

  2. iOS ARC内存管理

    iOS的内存管理机制,只要是iOS开发者,不管多长的时间经验,都能说出来一点,但是要深入的理解.还是不简单的.随着ARC(自动管理内存)的流行.iOS开发者告别了手动管理内存的复杂工作.但是自动管理内 ...

  3. iOS之内存管理(ARC)

    iOS的内存管理,相信大家都不陌生,之前是使用的MRC,由开发人员手动来管理内存,后来使用了ARC,来由系统管理内存.本文主要讲讲Autorelease,Core Foundation对象在内存管理方 ...

  4. 说说iOS与内存管理(上)

    http://www.cocoachina.com/ios/20150625/12234.html 说起内存管理,看似老生常谈,而真正掌握内存管理的核心其实并不简单.ARC/MRR以及“谁分配谁就负责 ...

  5. iOS的内存管理和引用计数规则、Block的用法以及三种形式(stack、malloc、global)

    学习内容 iOS的内存管理和引用计数规则 内存管理的思考方式 自己生成的对象自己持有 非自己生成的对象自己也能持有 自己持有的对象不需要时释放 非自己持有的对象不能释放 ARC有效时,id类型和对象类 ...

  6. iOS - OC 内存管理

    1.OC 基本内存管理模型 1.1 自动垃圾收集 在 OC 2.0 中,有一种称为垃圾收集的内存管理形式.通过垃圾收集,系统能够自动监测对象是否拥有其他的对象,当程序执行需要空间的时候,不再被引用的对 ...

  7. 总结 IOS 7 内存管理

    [iOS7的一些总结].iOS中的内存管理 我们知道,为了更加方便地处理内存管理问题,将开发人员从繁琐的内存的分配和释放工作中解放出来而专注于产品和逻辑,iOS提供了一种有效的方法, 即自动引用计数A ...

  8. IOS ARC内存管理,提高效率避免内存泄露

    本文转载至 http://blog.csdn.net/allison162004/article/details/38756263 Cocoa内存管理机制 (1)当你使用new.alloc.copy方 ...

  9. 【iOS系列】-iOS中内存管理

    iOS中创建对象的步骤: 1,分配内存空间,存储对象 2,初始化成员变量 3,返回对象的指针地址 第一:非ARC机制: 1,对象在创建完成的同时,内部会自动创建一个引用计数器,是系统用来判断是否回收对 ...

随机推荐

  1. IOS 上传头像-b

    感谢大神分享 1.首先,后台给了我这样的接口 1-后台数据接口 2.首先加上代理方法 <UIActionSheetDelegate,UINavigationControllerDelegate, ...

  2. MySQL在创建存储过程的时候,语法正确却提示You have an error in your SQL syntax

    我在使用MySQL工具编写MySQL存储过程的时候,明明语法正确,但是却一直提示You have an error in your SQL syntax. 比如下面一段代码 CREATE PROCED ...

  3. HTTP-304 NOT Modified

    http://www.douban.com/note/161120791/ http://blog.sina.com.cn/s/blog_4c98b9600100jd4z.html http://ww ...

  4. The Glorious Karlutka River =)

    sgu438:http://acm.sgu.ru/problem.php?contest=0&problem=438 题意:有一条东西向流淌的河,宽为 W,河中有 N 块石头,每块石头的坐标( ...

  5. King's Quest

    poj1904:http://poj.org/problem?id=1904 题意:国王有n个儿子,现在这n个儿子要在n个女孩里选择自己喜欢的,有的儿子可能喜欢多个,最后国王的向导给出他一个匹配,匹配 ...

  6. Android 两个Activity进行数据传送 发送

    Activity1:: Intent intent= new Intent(this, OtherActivity.class); String name = "heyiyong" ...

  7. 使用php对多维维数组排序。

    要多php的多维数组排序,可以使用php里的内置函数:array_multisort(); 语法:array_multisort(array1,sorting order,sorting type,a ...

  8. How to Setup Chroot SFTP in Linux (Allow Only SFTP, not SSH)

    1. Create a New Group Create a group called sftpusers. Only users who belong to this group will be a ...

  9. c++重载、覆盖和隐藏

    看以前的:http://www.cnblogs.com/youxin/p/3305688.html 答案:a.成员函数被重载的特征:overload(1)相同的范围(在同一个类中):(2)函数名字相同 ...

  10. poj2151

    求每只队伍都回答出题目,且至少有一只队伍回答出n道题的概率存在性问题我们可以转化为任意性问题用P(每支队伍都回答出题目)-P(每只队伍回答的题目数小于n)然后我们可以递推求解 ..,..,..] of ...