objc的内存管理详解

objc作为极少见的以引用记数来管理内存的动态语言,对于从其它语言转过来的开发者来说有 点陌生,甚至是对于MRC或者ARC有点束手无策,网上的文章一般不能透彻的分析其中的奥妙, 还好《objc高级编程》这本书为我们打开了objc引用记数的神秘面纱,这里将这本书中关于的 内存管理方面的精髓做个总结。

MRC

MRC(Manual Reference Counting),顾名思义即手动管理对象的引用记数,在xcode4.2之前, ios开发者都需要自己来管理对象的生命周期,其有四个准则:

  1. 自己生成的对象自己持有

     {
         id obj1 = [[NSObject alloc] init];
         id obj2 = [[NSObject new];
         id obj3 = [[NSObject copy];
         id obj4 = [[NSObject mutableCopy];
     }
    

    这四种方式(alloc/new/copy/mutableCopy,以及以他们开头的函数簇)生成的对象是自己 生成并持有的对象。

  2. 非自己生成的对象自己也能持用

     {
         id obj = [NSArray array];
         [obj retain];
     }
    

    对于非自己生成的对象,需要调用retain来持有该对象。

  3. 不需要自己持有的对象时释放

     {
         id obj = [[NSObject alloc] init];
         [obj release];
     }
    

    如果自己持有的对象不再需要,使用release释放。

  4. 无法释放非自己持有的对象

     {
         id obj = [NSArray array];
         [obj release];
     }
    

    上述release调用会导致奔溃,因为释放非自己持有的对象是非法的。

实现上述引用记数(垃圾回收的一种)有两种通用的方式,如下:

  1. 将引用记数(retainCount)保存在对象头中,调用alloc或者retain之后,retainCount +1,调用release之后,retainCount-1,当引用记数为0时,调用对象的dealloc方法来释 放当前的对象。
  2. 将引用记数和内存块地址保存在散列表(即引用记数表)中,引用记数的作用同上,内存 块地址用来追溯对象的内存地址。

苹果的实现方式为第2种方式,显然这种方式有利于调试和追溯内存空间。

* 谈到objc的内存管理就不得不说autoRelease(自动release),其管理内存的方式如下:
{
    id obj = [[NSObject alloc] init];
    [obj autorelease];
}
当autorelease调用发生时,obj对象会被注册进对应的autoreleasePool中,NSRunloop
此次循环结束的时候将pool中的对象废弃。所以对于注册进autoreleasePool中的对象,
并不是立即废弃,而是需要等循环结束来释放pool。

ARC

ARC(Automatic Reference Counting),即是编辑器帮助我们自动管理引用记数,大大地提升 了开发者的效率,是现在xcode的默认管理内存方式,其是通过给对象加上所有权修饰符来实 现的,一共有四种修饰符号:

  1. __strong修饰符(id类型和对象类型默认的修饰符号)

     {
         id __strong obj0 = [[NSObject alloc] init];/*对象A*/
         id __strong obj1 = [[NSObject alloc] init];/*对象B*/
         obj0 = obj1;/*obj0持有对象B的强引用,因此原先的对A强引用失效,故而废弃对象A*/
     }
    

    由此可见,__strong不止在变量作用域,在赋值上也能正确管理对象所有者。那么ARC是如何 来实现自动管理的呢?

      {
          id obj = [[NSObject alloc] init];
    
          /*编译器模拟代码*/
          id obj = objc_msgSend(NSObject, @selector(alloc));
          objc_msgSend(obj, @selector(init));
          objc_release(obj);
      }
    
      {
          id obj = [NSArray array];
    
          /*编译器模拟代码*/
          id obj = objc_msgSend(NSArray, @selector(array));
          objc_retainAutoreleaseReturnValue(obj);
          objc_release(obj);
    
          + (id) array {
              return [[NSArray alloc] init];
          }
    
          /*编译器模拟代码*/
          + (id) array {
              id obj = objc_msgSend(NSArray, @selector(alloc));
              objc_msgSend(obj, @selector(init));
              return objc_autoreleaseReturnValue(obj);
          }
      }
    

    上面两个代码块,清晰得展现除了__strong的模拟实现过程,即ARC会在适当的地方自动插入 release代码。特别注意上述参数传递过程的实现,中间借助autoreleasePool来过度。

  2. __weak修饰符(为了避免环形引用)

     id __weak obj1;
     {
         id obj0 = [[NSObject alloc] init];
         obj1 = obj0;
     }
     /*
         此处obj1为nil,因为对象的强引用为0时即被释放
     */
    

weak修饰符使我们能够使用对象,但是不持有对象。ARC是通过用weak表来记录weak变 量的,weak表与上文中的引用记数表相似,都是散列表的实现。一个对象被收回时,其在 weak表中的所有weak记录全部被销毁,其weak变量被赋值为nil。所以大量的weak修饰必 然会损耗性能,所以只在有循环应用危险的时候使用weak才是正确姿势。

对于weak修饰符还有一个比较有意思的事情,就是:使用附有weak修饰符的变量就是使用 autoreleasePool中的变量,这一点描述如下:

```
{
    id __weak obj1 = obj;
    NSLog(@"%@", obj1);

    /*编译器模拟代码*/
    id obj1;
    objc_initWeak(&obj1, obj);
    id tmp = objc_loadWeakRetained(&obj1);
    objc_autorelease(tmp);
    NSLog(@"%@", tmp);
    objc_destoryWeak(&obj1);
}
```

由以上可以得知,所有附有weak修饰的变量的使用,均会被注册到autoreleasePool中,使用 几次就会被注册几次,所以过多的使用weak也会造成问题。所以恰当使用weak很重要。

  1. __unsafe_unretained修饰符

此修饰符与weak一样,不会持有对象,但其不属于编译器管理的对象,所以是不安全的, 使用较少(可以在ios5之前,代替weak的作用)。

  1. __autoreleasing修饰符

比较少用。其调用的效果和MRC下调用autorelease的效果相同。

bridge

Objective-c中的对象(Foundation)与Core Foundation的对象没有任何区别,在MRC中, 两者之间可以自由转换,不需要使用额外的cpu资源。但是使用ARC来管理对象的情况下, 需要使用显式的转换,而且转换的安全性有所下降。如下有几种bridge书写方式。

  1. __bridge转换(赋值)

     {
         id obj = [[NSObject alloc] init];
         void *p = (__bridge void *)obj;
         id o = (__bridge id)p;
     }
    
  2. bridge_retained与bridge_transfer

     {
         id obj = [[NSObject alloc] init];
         /*变量p(Core Foundation)也持有对象*/
         void *p = (__bridge_retained void *)obj;
    
         /*被转换变量在被赋值给转换变量之后随之释放*/
         id obj = (__bridge_transfer id)p;
    
     }
    

引用记数作为垃圾回收最原始的方式,在objc中得以发扬光大,由此可见“技术并没有优劣 之分,在进行足够的探索和优化之后也能发挥巨大的作用”。上面是总结了objc内存管理的 一些关键要领,只有从大的引用记数的方向去把握objc的实现方式,才能真正领悟它。



Previous     Next
zhing /
Published under (CC) BY-NC-SA in categories ios  tagged with ios