美文网首页
objc的内存管理详解

objc的内存管理详解

作者: myzhing | 来源:发表于2016-12-21 22:03 被阅读0次

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,以及以他们开头的函数簇)生成的对象是自己
生成并持有的对象。

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

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

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

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

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

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

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

    {
        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来过度。

  1. __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的实现方式,才能真正领悟它。

相关文章

  • objc的内存管理详解

    objc作为极少见的以引用记数来管理内存的动态语言,对于从其它语言转过来的开发者来说有点陌生,甚至是对于MRC或者...

  • iOS Runtime

    objc_object id = objc_object [ isa、弱引用、关联对象、内存管理] objc_...

  • 内存管理

    ARC内存管理机制详解理解 iOS 的内存管理

  • [JVM系列]JVM内存管理详解

    JVM内存管理详解

  • 面试篇(一):内存管理你都知道哪些?

    引用计数器 在ObjC中内存的管理是依赖对象引用计数器来进行的,ObjC中内存的管理在ObjC中每个对象内部都有一...

  • iOS Autorelease Pool (自动释放池)

    我们都知道在内存管理中有2种释放内存,一种是[ objc release] 直接释放 还有一种是 [objc ...

  • Runtime应用1-关联对象

    使用 内存管理策略 内存策略属性修饰描述OBJC_ASSOCIATION_ASSIGN@property (ass...

  • ios面试攻略2

    七、内存管理 1.objc的内存管理简介 (1)如果您通过分配和初始化(比如[[MyClass alloc] in...

  • OC反汇编

    OC对象在内存中的存储 ARC下对象的管理 汇编中,编译器调用了objc_storeStrong方法,在objc源...

  • Java内存管理

    原文:Java内存管理原理及内存区域详解 一、概述: Java虚拟机在执行Java程序的过程中会把它所管理的内存划...

网友评论

      本文标题:objc的内存管理详解

      本文链接:https://www.haomeiwen.com/subject/enqcvttx.html