iOS与OS X多线程和内存管理 - 自动引用计数

前言

iOS与OS X多线程和内存管理 读书笔记(一) 自动引用计数

笔记

内存管理/引用计数

  1. 使用办公室的照明灯来做比喻,对象相当于照明灯,但是会有许多人需要这个照明灯,也就是持有对象。只要有人持有这个对象,就不能释放对象;若没人在需要对象,就释放对象。

  2. 例如:a = [class new]; b = a; 那么a生成对象时持有对象,b也持有对象。只有当a和b都不持有对象时,才能释放对象。

  3. 对于对象,有4个关键词。生成持有释放废弃

  4. 看看对应的操作:(以下的方法均是NSObject类的方法)

    1. 使用alloc/new/copy/mutableCopy开头的方法生成持有对象
    2. retain方法持有对象
    3. release方法释放对象
    4. dealloc方法废弃对象
  5. 自己生成的对象自己持有

    id obj = [[NSObject alloc] new]; // alloc方法生成并持有对象
    // or
    id obj = [NSObject new]; // new 和 alloc + init 一样
    

    另外copymutableCopy也是生成对象的副本,并持有。

  6. 非自己生成的对象,自己也能持有

    id obj = [NSMutableArray array]; // 对象存在了,但是obj对象不持有对象
    [obj retain]; // 现在obj已经持有对象了
    
  7. 不在需要自己持有的对象时释放

    id obj = [NSObject new]; // obj持有对象
    [obj release]; // obj不再需要对象时,释放对对象。 对象释放后就不能被访问到了。
    
    1. 不持有可以这样理解:可以使用对象,但是没有强行占用对象。
    2. 无法释放非自己持有的对象
    id obj = [NSMutableArray array]; // obj自己不持有对象
    [obj release]; // 程序崩溃,因为不能释放不是自己持有的对象
    
  8. 来看看如何实现alloc/retain/release/dealloc方法 (注意:这部分对于理解最为关键)。

    1. alloc的实现

         struct obj_layout {
               NSUInteger retained;
         }
      
      + (id)alloc {
            int size = sizeof(struct obj_layout) + 对象大小;
            struct obj_layout *p = (struct obj_layout *)calloc(1, size); // 分配一个长度为size的连续内存空间 p为该内存空间的第一个地址。这个内存空间的内存计量单位为一个obj_layout struct
            return (id)(p + 1); // 该内存空间的第一个内存单位放的是obj_layout这个结构体,之后是对象的内存空间,已经被置为0。返回的是指向对象内存空间的首地址的指针。
      }
      
    2. 看看retainCount的实现,这个方法用来查看对象的引用计数

      - (NSUInteger)retainCount {
            return ((struct obj_layout *) self)[-1].retained + 1;
      }
      // 因为self指向的是对象内存的首地址。将这个地址看成obj_layout,也就是地址计数以obj_layout结构体的内存大小为单位。-1就指向了对象上方存储obj_layout结构体的首地址。这样就可以取出retained。因为retained默认是0,所以需要加1。
      
    3. 根据2,可以方便推出retain方法是将retained值加一;而release方法是将retained值减一。注意当release时发现retained值等于0,那么就不执行减一操作了,直接执行dealloc方法,因为已经没有人只有该对象了。

    4. dealloc的实现

      - (void)dealloc {
            struct obj_layout *o = &((struct obj_layout *) self)[-1];
            free(o); // 释放之前由calloc分配的内存空间
      }
      
  1. autorelease

    当变量调用对象的autorelease方法时,说明他已经不持有这个对象了。但是这个对象并没有被释 放,还是可以被取到。

    autorelease的使用方法:

    1. 生成并持有NSAutoreleasePool对象
    2. 调用已分配对象的autorelease方法
    3. 废弃NSAutoreleasePool对象

    NSAutoreleasePool对象生成和废弃的这个期间,相当于对象的作用域。当废弃NSAutoreleasePool对象时,自动调用对象的release方法。来看看代码:

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain]; // 这个时候才会release对象
    

    注意:使用autorelease时,太多的需要autorelease的对象可能由于没有及时的release,会导致内存不足。PS:其实还是在dealloc的时候释放内存,但是因为在release时,若没有变量引用对象的话,会自动调用对象的dealloc方法,所以release也可以说是释放内存吧。

    看看下面代码:

    id array = [NSMutableArray arrayWithCapacity:1]; // array不持有对象
    id array = [[NSMutableArray alloc] initWithCapacity:1] autorelease]; // array也不持有对象
    

    上面两行代码是等价的。

  2. autorelease的实现

   - (id)autorelease {
         [NSAutoreleasePool addObject:self];
   }

   + (void)addObject:(id)anObj {
         NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool对象;
         if (pool != nil) {
             [pool addObject:anObj];
       } else {
             NSLog(@"不存在NSAutoreleasePool");
       }
   }

   - (void)addObject:(id)anObj {
         [array addObject:anObj]; // NSAutoreleasePool对象里面有一个array数组,用来存储需要autorelease的对象
   }

   - (void)drain {
         [self dealloc]; // 废弃NSAutoreleasePool对象
   }

   - (void)dealloc {
         for (id obj in array) {
             [obj release]; // 释放之前注册到NSAutoreleasePool的对象
       }
       [array release]; // 释放array数组
   }
  1. 注意不能autorelease NSAutoreleasePool对象

ARC规则

  1. 引用计数式内存管理的本质并没有改变,只是ARC自动帮我们处理“引用计数”的相关部分。

  2. 编译单位可以选择ARC是否有效。一个应用程序可以以混合ARC和非ARC的方式进行编译。

  3. NSObject *是指向NSObject类的指针。id相当于C语言中的void *

  4. 在ARC下,类型必须附上所有权修饰符,有一下几种类型

    1. __strong
    2. __weak
    3. __unsafe_unretained
    4. __autoreleasing
  5. __strong修饰符:是id类型和所有对象类型默认的所有权修饰符。

    id obj = [[NSObject alloc] init]; 
    // 上面实际的代码如下
    id __strong obj = [[NSObject alloc] init];
    

    再来看看一下代码:

    /* ARC下 */
    {
          id __strong obj = [[NSObject alloc] init];
    } // 出了作用域,obj对象自动被释放
    
    /* 非ARC下的等价代码 */
    {
          id obj = [[NSObject alloc] init];
          [obj release]; // 出作用域之前要记得释放obj对象,不然会导致内存泄漏
    }
    

    __strong修饰符表示对对象的强引用。在超出作用域时,强引用变量被废弃(obj指针被废弃),与此同时它引用的对象也会被释放(对象被释放)。

    看看对于非自己生成的对象:

    /* ARC下 */
    {
          id __strong obj = [NSMutableArray array]; // 这里的obj是持有对象的
          // do something here
    }
    
    /* 非ARC下的等价代码 */
    {
          id obj = [NSMutableArray array];
          [obj retain];  // obj要手动持有
          // do something here
    
          [obj release];  // 出作用域时要手动释放
    }
    
  6. __weak修饰符主要是为了防止相互引用。__weak指的是弱引用,也就是变量不能持有对象。

    {
          id __strong obj0 = [[NSObject alloc] init]; // obj0强引用对象,持有
          id __weak obj1 = obj0; // obj1弱引用对象,不持有
    }
    

    所以在出作用域时obj0废弃,不在有变量持有对象,对象被释放。可以通过检查附有__weak修饰符的变量是否为nil,来判断对象是否已经被释放。

  7. __unsafe_unretained作用和__weak一样,但是如名字所言,它是不安全的。当这种变量指向的对象被释放时,weak型的变量就被置成nil(正确的做法),但是unsafe的变量可能还指向某个地址(不安全的,因为那个地址的对象已经被释放了)。

  8. 来看看autorelease,先看段代码:(PS:有点迷糊)

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

    但是通常不需要再@autorelease里面显式的使用__autoreleasing修饰符。

  9. 在ARC有效时有以下规定:

    1. 不能使用retain/release/retainCount/autorelease方法
    2. 不能使用NSAllocateObject/NSDeallocateObject
    3. 要遵循方法的命名规则。alloc/new/copy/mutableCopy开头的方法,要返回自己生成并持有的对象。init开头的方法,基本只是对alloc方法返回的对象进行初始化并返回该对象。
    4. 不要显式调用dealloc方法。因为在对象需要废弃时,会自动调用该方法。在ARC无效时,要在dealloc方法里面调用[super dealloc];。但是在ARC有效时,不需要这样调用,ARC会自动帮你处理。
    5. 使用@autoreleasepool而不是NSAutoreleasePool
    6. 结构体中的成员变量不能是Objective-C对象
    7. id 和 void* 不懂,什么东西?这里还和__bridge有点关系
  10. 在ARC有效时,变量有各种修饰符。当然属性也会有各种修饰符。

    1. assign == __unsafe_unretained
    2. copy == __strong (但是赋值的是被复制的对象)
    3. retain == __strong
    4. strong == __strong
    5. unsafe_unretained == __unsafe_unretained
    6. weak == __weak
  11. 数组1.3.6 不懂

ARC的实现

  1. ARC由下面两者来实现

    1. clang(LLVM编译器)3.0以上
    2. objc4 Objective-C运行时库 493.9以上 (主要是 runtime/objc-arr.mm)
  2. __strong的实现

    {
          id __strong obj = [[NSObject alloc] init];
          // do something
    }
    
    /* 经过编译之后 */
    id obj = objc_msgSend(NSObject, @selector(alloc)); // 执行alloc方法,obj变量持有对象,也就是强引用
    objc_msgSend(obj, @selector(init)); // 执行init方法,进行一些初始化操作,无关ARC
    // do something
    objc_release(obj); // 在变量出作用域时,编译器自动释放对象
    
    {
          id __strong obj = [NSMutableArray array];  // obj是持有对象的
          // do something
    }
    
    /* 经过编译之后 */
    id obj objc_msgSend(NSMutableArray, @selector(array)); // 执行array方法,此时对象“照理来说”是已经被注册到NSAutoreleasePool里面,obj变量此时是不持有对象的
    objc_retainAutoreleasedReturnValue(obj); // 编译器帮obj持有注册到NSAutoreleasePool中的变量
    // do something
    objc_release(obj); // 变量要出作用域了,编译器自动释放对象
    
    /* array函数实质 */
    + (id)array {
          return [[NSMutableArray alloc] init];
    }
    
    /* 经过编译之后 */
    + (id)array {
          id obj = objc_msgSend(NSMutableArray, @selector(alloc)); // 生成对象,obj持有
          objc_msgSend(obj, @selector(init)); // 初始化,无关ARC
          return objc_autoreleaseReturnValue(obj); // 讲道理,这一步是要将对象注册到NSAutoreleasePool中,obj不再持有对象(可以说是obj将对象持有权转交给了NSAutoreleasePool)
    }
    

    按上面的说法来看,最终持有array对象的有2个:一个是NSAutoreleasePool,一个是obj。但是其实是做了优化的。objc_autoreleaseReturnValue(obj);这个函数观察到紧接着它自己调用的函数如果是objc_retainAutoreleasedReturnValue(obj);的话,就不会将对象注册到NSAutoreleasePool,直接将对象给obj让它持有就行了。这个优化就是因为__strong修饰符要持有对象,与其先将对象注册到NSAutoreleasePool,再从NSAutoreleasePool中拿到这个对象给obj持有一遍;还不如直接跳过注册,直接让obj持有对象。说到底,使用NSAutoreleasePool的原因就是怕没有变量持有对象,那么就取不到这个对象了,所以让NSAutoreleasePool暂时持有对象。

    PS:让NSAutoreleasePool暂时持有对象这个说法不知道靠不靠谱

    todo add a picture

  3. __weak的实现

    {
          id __weak obj1 = obj; // 这里obj是强引用
          // do something
    }
    
    /* 编译之后 */
    id = obj1;
    obj1 = 0;
    objc_storeWeak(&obj1, obj);
    // do something
    objc_storeWeak(&obj1, 0);
    

    objc_storeWeak(&obj1, obj);函数将obj指向对象的地址作为key,将obj1变量的地址作为value,注册到weak表中,并将obj1指向对象。因为一个对象可以赋给多个值,所以一个key可以对应多个值。如果第二个参数是0的话,就将obj1这个变量的地址从weak表中删除(删除value为obj1地址的那条记录,废弃obj1对象)。PS:这么说的话,其实将obj1变量的地址当成key,更方便理解。

    PS:我们来看看另外一种情况,就是释放对象时,但是weak变量还存在。这个时候会将weak变量置为nil。这是如何实现的呢?

    1. 先将weak表中key为这个对象的地址的记录找出来
    2. 将该地址对应的所有的weak变量找出来,将他们置为nil
    3. 删除这条key为对象地址的记录

    由上面说明可以知道,大量使用weak变量也会消耗相应的CPU资源,所以只在需要避免循环引用时使用。

  4. 看看weak和unsafe_unretained的区别

    {
          id __weak obj = [[NSObject alloc] init];
          // do something
    }
    /* 编译之后 */
    id = obj;
    id tmp = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(tmp, @selector(init));
    objc_initWeak(&obj, tmp);  // 在weak表中插入对象,将obj指向对象
    objc_release(tmp); // 释放对象后,不在有变量持有对象,这时候会将obj置为nil。安全
    // do something
    objc_destroyWeak(&obj); // 删除记录(上一步已经做了,这里就不做了),废弃obj变量
    
    {
          id __unsafe_unretained obj = [[NSObject alloc] init];
          // do something
    }
    
    /* 编译之后 */
    id obj = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    objc_release(obj); // 因为没有变量持有对象,所以释放对象,但是obj变量还指向了原对象。危险
    // do something
    
  5. 还有就是要注意使用__weak变量时

    NSLog(@"%@", obj); // 假设这里的obj是weak变量
    /* 编译之后 */
    id tmp = objc_loadWeakRetained(&obj);
    objc_autorelease(tmp);  // 这两句说的是将obj弱引用的对象交给NSAutoreleasePool持有,以保证这个对象可以被取到。
    NSLog(@"%@", tmp); // 做正事
    

    上面说的这个是,当使用weak变量的时候,会先将weak变量指向的对象交给NSAutoreleasePool持有,以保证这个对象万一被强引用它的变量释放的时候,还是可以取到。但是当多次使用weak变量的话,最好先将weak变量的对象给一个变量,让它强引用,这样就不用多次去和NSAutoreleasePool打交道,耗时间了。

    id tmp = obj; // 让tmp强引用对象,这里会发生一次和NSAutoreleasePool的交互,因为使用到了obj这个weak变量。这一步过后tmp和NSAutoreleasePool都持有对象
    // 使用tmp做正事,而不是频繁使用obj
    
  6. __autoreleasing的实现

    @autoreleasepool {
          id __autoreleasing obj = [[NSObject alloc] init];
    }
    
    /* 编译之后 */
    id pool = objc_autoreleasePoolPush();
    id obj = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    objc_autorelease(obj); // 交给NSAutoreleasePool持有
    objc_autoreleasePoolPop(pool); // 废除NSAutoreleasePool,释放对象
    

    看看对于非自己生成并持有的对象

    @autoreleasepool {
          id __autoreleasing obj = [NSMutableArray array];
    }
    
    /* 编译之后 */
    id pool = objc_autoreleasePoolPush();
    id obj = objc_msgSend(NSMutableAttay, @selector(array)); // 不持有
    objc_retainAutoreleasedReturnValue(obj); // 又重新持有对象。。。
    objc_autorelease(obj); // 又交给NSAutoreleasePool持有
    objc_autoreleasePoolPop(pool); // 废除NSAutoreleasePool,释放对象
    
  7. uintptr_t _objc_rootRetainCount(id obj);函数可以获得obj对象当前的引用计数,也就是有多少个变量强引用该对象。注意注册到NSAutoreleasePool中的对象也算一次引用计数。也就是如同上面一直说的,可以说是NSAutoreleasePool持有对象。注意,这个函数不一定可信。。。

PS

呕心沥血啊。看得要吐了。。。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器