ARC下的内存管理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hbw1992322/article/details/52121089

  • ARC是谁,它能干嘛?

    ARC全称叫Automatic Reference Counting。简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。简单地理解ARC,就是通过指定的语法,让编译器(LLVM 3.0)在编译代码时,自动生成实例的引用计数管理部分代码。有一点,ARC并不是GC,它只是一种代码静态分析(Static Analyzer)工具[1]。

    之前听ARC开发者说过一句话:“哥们,你要知道我是全职做这个事(ARC)的,你还担心什么”。我觉得大多数人自己用MRC的方式对内存的管理能显然无法超过Apple里面开发ARC的人对内存的管理水准,所以放心的用ARC吧。
    但是ARC的管理权限是有限的,仅限于对于NSObject类对象的管理。而我们在IOS开发中会用到的Core Foundation类型(CFType)的对象ARC就鞭长莫及了。

  •  堆与栈
    我们首先了解堆与栈的概念
    ------"栈“者,存储货物或供旅客住宿的地方,可引申为仓库、中转站(FILO)。
    栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:
    1.函数的返回地址和参数
    2. 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
程序会将正在执行的函数入栈,执行完毕之后将此函数出栈。
    ------堆就是堆
    堆是程序员申请的一块连续的内存,如使用malloc()申请一块内存缓冲区,当不再使用这块缓冲区的时候,可以调用free()函数释放相应的内存,将其返回给堆。

    现在我来举个“栗子”:
    -(void)fun{
        @autoreleasepool{
            NSData *time = [NSData data];
        }
    }
    这是一个oc的函数,如果我们调用这个函数,则中就会push进这个函数帧,此函数帧中包含了一个局部变量time,这是一个指针类型的变量,变量前面的*说明它是一个指针(其实在oc里面指针指向的是一个结构体,在此提一下不深究),这个指针保存了NSData实例在内存中的地址。 而赋值运算符右边的 [NSData data]返回了一个地址赋值给time,这个地址所表示的内存块保存在中。 由于我们申请了这块内存,所以我们需要手动的释放他,所以在函数中写了@autoreleasepool{},在@autoreleasepool{}执行完之后这块内存就释放掉了。 这是在MRC的情况下一种释放内存的方法,而在ARC中对于NSObject类型的对象系统会自动进行管理,我们只要写成:
    -(void)fun{
            NSData *time = [NSData data];
     }
就可以了,执行完fun函数之后堆中的内存会由ARC管理释放掉。
我们可以使用NSLog(@"%p", time); 打印这块内存的地址;使用NSLog(@"%@", time);打印内存中的内容。 
    我们所讨论的内存管理其实是在讨论如何管理堆,栈中的帧在调用完之后会自动释放,而堆是不会自动释放的。
    
  • ARC出山
    当我们在xcode上使用oc写程序的时候,系统会为每一个实例对象进行引用计数,引用是指针的另外一种叫法,说白了就是计算有多少指针指向它(只看强引用)。当一个实例对象没有指针指向它的时候,ARC就出来把这块内存回收了。
    调用free()函数来销毁对象是清理的最干净的方法。使用ARC,只能通过增加或减少对兑现搞的引用数来保留或者销毁对象。当引用数为0的时候,他才会被销毁。
    如果你已经用xcode写过oc程序了,那么你回忆一下你是如何新建一个类的。当我们要新建一个自定义的类的时候我们会在xcode界面的左侧项目文件区域右键鼠标,选择new File...,然后在弹出的对话框中选择Source,这时候重点来了,你接下来选择的会是一个叫“Cocoa Touch Class”的东西。是的,这就意味着你新建了一个cocoa类,当新建完成之后我们进入新建类的h文件就可以看到,它是继承了NSObject的。之前我提到过,ARC会管理好NSObject类型,但是不会管理CF类型。但是,这并不意味着我们可以对所有的NSObject类型高枕无忧了,你用的不好照样会出现内存泄漏^_^。因为,有一种情况叫做强引用循环。    
           ---- 强循环引用       
           什么叫做循环引用呢,现在我来举个“栗子”:
现在有一个类叫做Father ,另外还有一个类叫做Son,均为继承于NSObject。
Father Class:
@interface Father:NSObject
@property(nonatomic,strong)Son *son;
@end
Son Class:
@interface Son:NSObject
@property(nonatomic,strong)Father *father;
@end

int main(){
    Father *f = [[Father alloc] init];
    Son *s = [[Son alloc] init];
    f.son = s;
    s.father = f;
}
我们看到两个property均设为了strong,也就是强引用,而现在s,f两个对象事实上是相互引用的,这样的话ARC是无法释放两个对象的。这种情况就是我们在ARC情况下应该避免的强循环引用。关于如何xcode下如何检测内存泄漏可以看下这篇文章[2]。
            那么我们应该如何避免强循环引用呢?说起来很简单,把强循环引用改成弱循环引用就行了啊!对于上面的代码,我们应该将Son Class中的Father属性改为弱引用:@property(nonatomic,weak)Father *father; 
            如果对象间是父子关系,那么为了避免强循环引用,通常需要遵守此规则:父对象拥有子对象,但是子对象不能拥有父对象
            一般来说,我们对于对象属性的修饰会遵循 [3]:
            (1)strong还是weak
              说到底就是一个归属权的问题。小心出现循环引用导致内存无法释放,或者需要引用的对象过早被释放。大体上:IBOutlet可以为weak,NSString为copy或strong,Delegate一般为weak,基础类型用assign,不过要注意具体使用情况。
            (2)outlet使用strong还是weak
             官方文档建议一般outlet属性都推荐使用weak,不是直接作为main view里面一个subview直接显示出来,而是需要通过实例化创建出来的view,应该使用 strong(自己创建的自己当然要保持引用了)。但是要注意使用 weak时不要丢失对象的所有权,否则应该使用strong。
            (3)delegate使用strong还是weak
             delegate主要涉及到互相引用和crash(引用被释放)问题,为了防止这两个问题发生,delegate一般使用weak。

  • ARC退下
      前面说了ARC管不了CFTypes的对象,所以当遇到Core Foundation时,ARC就可以退下了。     
     Core Foundation 对象必须使用CFRetainCFRelease来进行内存管理。那么当使用Objective-C 和 Core Foundation 对象相互转换的时候,必须让编译器知道,到底由谁来负责释放对象,是否交给ARC处理。只有正确的处理,才能避免内存泄漏和double free导致程序崩溃。
        根据不同的需求,有三种转换方式:
        (1) __bridge 不改变对象所有权
            比如:NSDictionary *option = [......];(这是一个NSObject对象),
                      CFDictionaryRef option_dic = (__bridge CFDictionaryRef )option;
                      这时候我们不用写CFRelease(option_dic ),因为它不具有option的所有权,option还是归ARC管,不关你的事。
        (2)__bridge__retained  解除ARC所有权,也就是说这个对象由我自己来管理
        (3)__bridge__transfer   给予ARC所有权

        在实际的写程序过程中我遇到过overrelease的情况,就是重复释放,CFRelease()不能传入一个已经dealloc的对象。所以我们应该先搞清楚各个CFTypes之间的依赖关系,并且在写CFrelease的时候最好写成
if(x != null){  CFRelease(x); x = null;  }。 关于overrelease可以看这个很简单的小例子[4]感受一下。
看完栗子[4]之后讲一下我自己实际遇到的问题:
以上的代码中注意一下
CGImageRef imageRef = CGimageSourceCreateImageAtIndex(source,0,option_dict);
我在之后的代码中写了一下的释放语句:
是的,自信点击command+R运行程序,直接在CFRelease(source)那里挂掉(-。-)
网上的人说只要看到类似于CGimageSourceCreateImageAtIndex这样的方法中有create关键字就应该进行CFRelease,我遵循了。但是实际运行的时候发现是overrelease。后来我去看CGimageSourceCreateImageAtIndex这个方法的API,里面的注释写到:
这个应该是返回了一个指针吧,而且看source创建的代码source中只放有一张图片,也就是说source其实应该是image的容器,很有可能是这样的情况:
如果没推测错的话这个情形和[4]中遇到的情况是完全一样的道理。所以我那样的释放内存方式才会造成overRelease。
最后关于free()的内容,可以看下[5]参考下。关于内存的操作还可以看下这篇文章[6][7][8]
这篇文章写得还是非常浅的,说的不对的地方一起讨论。

Reference:

展开阅读全文

没有更多推荐了,返回首页