• iOS的内存管理


      在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*"
  • 相关阅读:
    【笔记篇】(理论向)快速傅里叶变换(FFT)学习笔记w
    【学术篇】bzoj2440 [中山市选2011]完全平方数
    【笔记篇】斜率优化dp(五) USACO08MAR土地购(征)买(用)Land Acquisition
    【笔记篇】斜率优化dp(四) ZJOI2007仓库建设
    【笔记篇】斜率优化dp(三) APIO2010特别行动队
    【笔记篇】斜率优化dp(二) SDOI2016征途
    【笔记篇】斜率优化dp(一) HNOI2008玩具装箱
    【笔记篇】单调队列优化dp学习笔记&&luogu2569_bzoj1855股票交♂易
    usr/include/php5/ext/pcre/php_pcre.h:29:18: fatal error: pcre.h 错误解决
    ubuntu 使用apt-get install 安装php5.6--php7
  • 原文地址:https://www.cnblogs.com/dongliu/p/5478105.html
Copyright © 2020-2023  润新知