自动引用计数 (读书笔记一)

239 阅读7分钟

一. 引用计数式内存管理的思考方式

  1. 自己生成的对象自己持有(以下是常用的初始化方法)
    • alloc
    • new
    • copy (生成并持有不可变对象
    • mutableCopy (生成并持有可变对象
  2. 非自己生成的对象,自己也能持有
    • array (使用array初始化的时候,取得的对象不是自己所持有的,需要持有的时候,需要使用retain方法)

    通过retain方法,非自己生成的对象跟用第一点的方法生成并持有的对象一样,成为了自己所持有的。

  3. 不再需要自己持有对象时释放
    • release -- 使用该方法可以释放通过第一点的方法或者retain方法持有的对象。
    • autorelease -- 使用该方法,可以使取得的对象存在,但自己不持有对象。autorelease方法提供的功能:使对象在超出指定的生存范围时能够自动并正确地释放。
  4. 无法释放非自己持有的对象

    释放非自己持有的对象,会导致应用程序崩溃!!!

二. 所有权修饰符

Objective-C为了处理对象,可将变量类型定义为id类型或各种对象类型。所谓对象类型就是指向NSObject这样的Objective-C类的指针,id类型则用于隐藏对象类型的类名部分。ARC有效的时候,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符。所有权修饰符一共有4种。

  • __strong 修饰符

    • __strong修饰符是id类型和对象类型默认的所有权修饰符。
    id obj = [[NSObject alloc] init];
    id __strong obj = [[NSObject alloc] init];
    // 两者是相同的
    

    像这样初始化的时候,id变量已被附加了所有权修饰符。 __strong修饰符表示对对象的“强引用”。持有者引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

    • 自己生成并持有对象的情况
    {
        // 自己生成并持有对象
        id __strong obj = [[NSObject alloc] init];
        // 因为变量obj为强引用,所以自己持有对象
    }   // 变量obj超出其作用域,强引用失效,释放自己持有的对象
    
    • 取得非自己生成并持有对象的情况
    {
        // 取得非自己生成并持有对象
        id __strong obj = [NSMutableArray array];
        // 因为变量obj为强引用,所以自己持有对象
    }   // 变量obj超出其作用域,强引用失效,释放自己持有的对象
    
    • 被__strong修饰的变量之间可以相互赋值

    思考一下:

    id __strong obj0 = [[NSObject alloc] init]; // 对象A
    id __strong obj1 = [[NSObject alloc] init]; // 对象B
    id __strong obj2 = nil; 
    
    obj0 = obj1;
    obj2 = obj0;
    // 问题:对象A和对象B分别被谁持有?
    
    obj0 = nil;
    obj1 = nil;
    obj2 = nil;
    // 问题:对象A和对象B分别被谁持有?
    

    过程解答:

    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
     */
     
    obj2 = obj0;
    /*
     * obj2持有由obj0赋值的对象B的强引用
     */
     
    /* 
     * 答案:对象A被废弃
     *      对象B被obj0、obj1、obj2持有
     */
    
    obj0 = nil;
    /*
     * 因为nil被赋值了obj0,所以对对象B的强引用失效
     */
     
    obj1 = nil;
    /*
     * 因为nil被赋值了obj1,所以对对象B的强引用失效
     */
     
    obj2 = nil;
    /*
     * 因为nil被赋值了obj2,所以对对象B的强引用失效
     * 对象B的所有者不存在,因此废弃对象B
     */
     
    /* 
     * 答案:对象A被废弃
     *      对象B被废弃
     */
    

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

  • __weak 修饰符

    看到这个修饰符,必然就会想到“循环引用”,循环引用是开发中很常见的问题。 常见的循环引用:互相强引用、对自身强引用

    • 互相强引用
    {
        id test0 = [[Test alloc] init]; // 对象A
        /*
         * test0持有Test对象A的强引用
         */
         
        id test1 = [[Test alloc] init]; // 对象B
        /*
         * test1持有Test对象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_。
         * 因此,发生了内存泄漏。
         */
    

    互相强引用

    内存泄漏:应当废弃的对象在超出其生存周期后继续存在。

    • 对自身的强引用
    id test = [[Test alloc] init];
    [test setObject:test];
    

    对自身的强引用

    这时候,__weak修饰符就有作用了,__weak修饰符与__strong修饰符相反,提供弱引用,弱引用不能持有实例对象。同时,在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态,我们来看一下示例代码:

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

    该示例代码的运行结果:

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

    下面来分析一下对象的持有情况:

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

    通过这个示例代码可以知道:使用__weak修饰符可避免循环引用。通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。

  • __unsafe_unretained 修饰符

    • __unsafe_unretained修饰符是不安全的修饰符,尽管ARC式的内存管理是编译器的工作,但被__unsafe_unretained修饰的变量不属于编译器的内存管理对象。
    • 和被__weak修饰的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会被立即释放。
    • 在使用__unsafe_unretained修饰符的时候,赋值给被__strong修饰的变量的时候有必要确保被赋值的对象确实存在。
  • __autoreleasing 修饰符 ARC有效的时候不能使用autorelease方法,也不能使用NSAutoreleasePool类。虽然autorelease不能直接使用,但在ARC有效的时候是起作用的。

    • ARC无效时的autorelease
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      id obj = [[NSObject alloc] init];
      [obj autorelease];
      [pool drain];
      
    • ARC有效时
      @autoreleasepool {
         id __autoreleasing obj = [[NSObject alloc] init];
      }
      

    显然,在ARC里,用"@autoreleasepool块"来替代"NSAutoreleasePool"类对象生成、持有以及废弃。

    那么,__autoreleasing 修饰符扮演的是一个什么角色呢?

    对象赋值给被__autoreleasing修饰的变量时,相当于在ARC无效的时候调用对象的autorelease方法,即对象被注册到autoreleasepool。

ps: 本文是作者看书的时候,为了方便记忆以及复习所记录下来的,很多地方引用了书中的例子,望各位路过的大佬不要介意。同时,也希望各位路过的大佬有所收获,新年快乐。