1

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

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

2.alloc方法

+ alloc
+ allocWithZone:
class_creatInstance
calloc

调用alloc方法首先调用allocWithZone:类方法,然后调用class_creatInstance函数,最后调用calloc来分配内存块。

3.retainCount/retain/release 方法

- retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
-  retain
__CFDoExternRefOperation
CFBasicHashAddValue
- retainCount
__CFDoExternRefOperation
CFBasicHashRemoveValue //CFBasicHashRemoveValue 为0时,-release调用dealloc

各个方法都通过同一个__CFDoExternRefOperation函数,调用一系列名称相似的函数。并且从函数名看出苹果采用散列表(引用计数表)来管理引用计数,表键值为内存块地址的散列值。然而GNUStep将引用计数保存在对象占用内存块头部的变量中(objc_layout这个结构体中)。

  • 内存块头部管理引用计数的好处:

    1. 少量代码皆可完成
    2. 能够统一管理引用计数内存块与对象内存块。
  • 引用技术表管理引用计数的好处:
    1. 对象内存快的分配无需考虑内存块头部

    1. 引用计数表各记录中存有内存块地址,可从各个记录追溯到各个内存块。
第二条特征在调试时很重要,即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有被损坏,就能够确认各个内存块的地址

4.autorelease方法

  1. NSAutoreleasePool是通过以AutoreleasePoolPage为结点的双向链表来实现的。AutoreleasePoolPage是一个C++实现的类,类结构如图:
    图例

    • magic 用来校验 AutoreleasePoolPage 的结构是否完整。
    • next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin()。
    • thread 指向当前线程。
    • parent 指向父结点,第一个结点的 parent 值为 nil。
    • child 指向子结点,最后一个结点的 child 值为 nil。
    • depth 代表深度,从 0 开始,往后递增 1。
    • hiwat 代表 high water mark。
  2. AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址内存结构如图:
    图片描述
  3. 在Cocoa框架中,NSRunloop每次循环过程中NSAutoreleasePool对象被生成或废弃。在大量产生autorelease对象时,只要不废弃NSAutoreleasePool那么生成的对象就不能被释放,在此情况下有时会产生内存不足的现象,因此有必要适当的生成,持有和废弃NSAutoreleasePool。通常在使用Objective-C,无论调用哪一个对象的autorelease/retain方法,实现上都是调用NSObject类的autorelease/retain实例方法,但是对于NSAutoreleasePool类,autorelease/retain实例方法已被重写,因此运行时会出错(exception)。autorelease实际上把对象的释放时机交给NSAutoreleasePool管理,使用方法如下:

    • 生成并持有NSAutoreleasePool对象。

      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 等同于 objc_autoreleasePoolPush()
    • 调用已分配对象的autorelease实例方法。

      id obj = [[NSObject alloc] init];
      [obj autorelease]; // 等同于 objc_autorelease()obj
    • 废弃NSAutoreleasPool对象(自动调用分配对象的release)。

      [pool drain]; // 等同于 objc_autoreleasePoolPop(pool)

5.ARC规则

ARC(Automatic Reference Counting)是编译阶段自动做了retain/release,原先需要手动添加处理引用计数的代码可以自动地由编译器完成,但实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行时库协助。同一程序中按文件单位可以选择ARC有效和无效。Core Foundation中的malloc()或者free()等,还是需要自己手动进行内存管理。ARC规则如下

  1. 不能使用retain/release/retainCount/autorelease。
  2. 不能使用NSAllocateObject/NSDeallocateObject。
  3. 必须遵守内存管理的方法命名规则。
  4. 不要显式调用dealloc。
  5. 使用@autoreleasepool块代替NSAutoreleasePool。
  6. 不能使用区域。
    虽说ARC有效时,不能使用区域(NSZone)。不管ARC是否有效,区域在现在的运行时系统(编译器宏__OBJC2__)中已单纯的被忽略
  7. 对象型变量不能作为C语言结构体的成员。

    struct Data {
        NSMutableArray *array; /* error: ARC forbids Objective-C objs in structs or unions  NSMutableArray *array */
    }
    /* 要把对象型白能量加入到结构体成员中时,可强制转换为void *(见下一条规则)或是附加“__unsafe_unreatained”修饰符。但是附有“__unsafe_unreatained”修饰的变量不属于编译器的内存管理对象,可能造成内存泄露或者崩溃。*/
     struct Data {
        NSMutableArray __unsafe_unreatained *array; /
    }
  8. 显式转换“id” 和 “void”。

    • 在MRC下"id"和"void *"可以强制转换,但在ARC下编译器报错,代码如下:

      id obj = [[NSObject alloc] init];
      void *p = obj;   // ARC编译报错
      
      id o = p;  // ARC编译报错
      [o release];
    • ARC情况下要用 "bridge转换",但是ARC不推荐此用法,这种转换经常用在Objective-C和Core Foundation对象之间的相互变换。

      • __bridge转换:只做类型转换,在不改变对象的引用计数,可能会造成悬垂指针而导致程序崩溃。
      • __bridge_retained转换:可使目标变量也持有赋值对象。对象引用计数加1。以下是ARC和MRC等价的代码

        // ARC
        id obj = [[NSObject alloc] init];
        void *p = (__bridge_retained void *)obj;
        // 等效 MRC 代码
        id obj = [NSObject alloc] init];
        void *p = (void *)obj;
        [(id)p retain];
      • __bridge_transfer转换:被转换的变量所持有的对象在赋值给目标变量后随之释放。

        // ARC 
        ① (void)(__bridge_transfer id)p; //指定返回结果为void ,不利用返回值 对象引用计数减1
        ② id obj = (__bridge_transfer id)p; // 如果返回值赋值给目标变量,转换后对象引用计数不变
        // 等效 MRC 代码
        ① [p release];
        ② id obj = (id)p; [obj retain];[(id)p release];

6.所有权修饰符

  1. __strong修饰符:对对象实例的强引用,是id类型或对象类型的默认所有权修饰符。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象
    会随之释放。

    // 以下两行代码是等效的
    id obj = [[NSObject alloc] init];
    id __strong obj = [[NSObject alloc] init];
  2. __weak修饰符:提供弱引用,弱引用不能持有对象实例,与__strong修饰符相反。可用来解决循环引用造成的内存泄露问与题。
  3. __unsafe_unretained修饰符:不安全的所有权修饰符,尽管ARC式的内存管理属于编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象,既不持有对象的强引用也不持有对象的弱引用,对象的可能随时被释放,访问释放的对象运行时会崩溃。
  4. __autoreleasing修饰符:对象变量要为自动变量(包含局部变量、函数以及方法参数)。利用“__objc_autoreleasePoolPrint()”调试autoreleasepool上的对象

    • 等价于MRC时调用对象的autorelease方法。以下两段代码等效:

      // MRC
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      id obj = [[NSObject alloc] init];
      [obj autorelease];
      [pool drain];
      // ARC
      @autoreleasepool {
          id __autoreleasing obj = [[NSObject alloc] init];
      }
    • 显式的附加__autoreleasing修饰符和显式度附加__strong修饰符一样“罕见”,有些情况下非
      显式的声明__autoreleasing修饰符也是可以的,这些情况会自动将对象注册到自动释放池。
      1.非自己生成并持有的对象

      @autoreleasepool {
          id  obj = [NSMutableArray array];
          // 变量obj为对象的强引用(默认__strong),编译器判断方法名后(非alloc/new等)自动注册大autoreleasepool
      }

      2.id的指针和对象的指针(二级指针)在没有显示的指定时会被附加上__autoreleasing修饰符。

       NSError *error = nil; //所有权为__strong类型
       NSError **pError = &error; //编译出错, NSError * (对象)默认为_autoreleasing类型,赋值前后所有权不匹配。
       NSError * __strong *pError = &error; //编译正确

      3.自己生成并持有的对象作为返回值

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

      4.虽然__weak修饰符是为了避免循环引用而使用的,但访问附有__weak修饰符的变量时实j 际上必定要访问注册到autoreleasepool的对象为了避免该对象被废弃,注册到autoreleasepool之后可以保证在autoreleasepool块结束之前对象存在。

       id __weak obj1 = obj0;
       NSLog(@"class = %@",[obj1 class]);
       // 以下代码和以上代码相同
       id __weak obj1 = obj0;
       id __autoreleasing tmp = obj1;
       NSLog(@"class = %@",[tmp class]);
       

7.引用计数查看

Apple 提供一些方法查看对象的引用计数,但是并不能完全信任这些函数提供的引用计数值。对于已释放的对象一级不正确的对象地址,有时 也返回”1“,在多线程中,因为存在竞态条件的问题,所以取得的的数值不一定可信。

 [object retainCount]; //得到object的引用计数,此方法仅仅适用于MRC
  _objc_rootRetainCount(object); // ARC和MRC适用,头文件声明:OBJC_EXTERN int _objc_rootRetainCount(id);

windtersharp
216 声望22 粉丝

看到巨人的肩膀!