zoukankan      html  css  js  c++  java
  • ARC下需要注意的内存管理

    ARC下需要注意的内存管理

    之前发了一篇关于图片加载优化的文章,还是引起很多人关注的,不过也有好多人反馈看不太懂,这次谈谈iOS中ARC的一些使用注意事项,相信做iOS开发的不会对ARC陌生啦。
    这里不是谈ARC的使用,只是介绍下ARC下仍然可能发生的内存泄露问题,可能不全,欢迎大家补充。

    Ps:关于ARC的使用以及内存管理问题,强烈建议看看官方文档,里面对内存管理的原理有很详细的介绍,相信用过MRC的一定看过这个。

    另也有简单实用的ARC使用教程:ARC Best Practices


    在2011年的WWDC中,苹果提到90%的crash是由于内存管理引起的,ARC(Automatic Reference Counting)就是苹果给出的解决方案。启用ARC后,开发者不需要担心内存管理,编译器会为你处理这一切(注意ARC是编译器特性,而不是iOS运行时特性,更不是其他语言中的垃圾收集器)。
    简单来说,编译器在编译代码时,会自动生成实例的引用计数代码,帮助我们完成之前MRC需要完成的工作,不过据说除此之外,编译器也会执行某些优化。

    ARC虽然能够解决大部分的内存泄露问题,但是仍然有些地方是我们需要注意的。

    循环引用

    循环引用简单来说就是两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。比如声明一个delegate时一般用weak而不能用retain或strong,因为你一旦那么做了,很大可能引起循环引用。

    这种简单的循环引用只要在coding的过程中多加注意,一般都可以发现。
    解决的办法也很简单,一般是将循环链中的一个强引用改为弱引用就可解决。
    另外一种block引起的循环引用问题,通常是一些对block原理不太熟悉的开发者不太容易发现的问题。

    block引起的循环引用

    我们先看看官方文档关于block调用时的解释:Object and Block Variables

    When a block is copied, it creates strong references to object variables used within the block. If you use a block within the implementation of a method:

    1. If you access an instance variable by reference, a strong reference is made to self;
    2. If you access an instance variable by value, a strong reference is made to the variable.

    主要有两条规则:
    第一条规则,如果在block中访问了属性,那么block就会retain住self。
    第二条规则,如果在block中访问了一个局部变量,那么block就会对该变量有一个强引用,即retain该局部变量。

    根据这两条规则,我们可以知道发生循环引用的情况:

    对象对block拥有一个强引用,而block内部又对外部对象有一个强引用,形成了闭环,发生内存泄露。

    怎么解决这种内存泄露呢?
    可以用block变量来解决,首先还是看看官方文档怎么说的:
    Use Lifetime Qualifiers to Avoid Strong Reference Cycles

    In manual reference counting mode, __block id x; has the effect of not retaining x. In ARC mode, __block id x; defaults to retaining x (just like all other values). To get the manual reference counting mode behavior under ARC, you could use __unsafe_unretained __block id x;. As the name __unsafe_unretained implies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use__weak (if you don’t need to support iOS 4 or OS X v10.6), or set the __blockvalue to nil to break the retain cycle.

    官网提供了几种方案,我们看看第一种,用__block变量:

    在MRC中,__block id x不会retain住x;但是在ARC中,默认是retain住x的,我们需要
    使用__unsafe_unretained __block id x来达到弱引用的效果。

    那么解决方案就如下所示:

    看看官方的文档吧,也建议你自己写个demo测试一下。

    可以注意到repeats参数,一次性(repeats为NO)的timer会再触发后自动调用invalidated,而重复性的timer则不会。
    现在问题又来了,看看下面这段代码:

    这个是很容易犯的错误,如果这个timer是个重复性的timer,那么self对象就会被timerretain住,这个时候不调用invalidate的话,self对象的引用计数会大于1,dealloc永远不会调用到,这样内存泄露就会发生。

    关于timer其实有挺多可以研究的,比如其必须在runloop中才有效,比如其时间一定是准的吗?这些由于和本章主题不相关,暂时就不说了。

    关于performSelector:afterDelay的问题

    我们还是看看官方文档怎么说的,同样也希望大家能写个demo验证下。

    大概意思是系统依靠一个timer来保证延时触发,但是只有在runloopdefault mode的时候才会执行成功,否则selector会一直等待run loop切换到default mode
    根据我们之前关于timer的说法,在这里其实调用performSelector:afterDelay:同样会造成系统对target强引用,也即retain住。这样子,如果selector一直无法执行的话(比如runloop不是运行在default model下),这样子同样会造成target一直无法被释放掉,发生内存泄露。
    怎么解决这个问题呢?
    其实很简单,我们在适当的时候取消掉该调用就行了,系统提供了接口:

    这个函数可以在dealloc中调用吗,大家可以自己思考下?
    关于NSNotification的addObserver与removeObserver问题
    我们应该会注意到我们常常会再dealloc里面调用removeObserver,会不会上面的问题呢?
    答案是否定的,这是因为addObserver只会建立一个弱引用到接收者,所以不会发生内存泄露的问题。但是我们需要在dealloc里面调用removeObserver,避免通知的时候,对象已经被销毁,这时候会发生crash.

    C 语言的接口

    C 语言不能够调用OC中的retain与release,一般的C 语言接口都提供了release函数(比如CGContextRelease(context c))来管理内存。ARC不会自动调用这些C接口的函数,所以这还是需要我们自己来进行管理的.

    下面是一段常见的绘制代码,其中就需要自己调用release接口。

    总的来说,ARC还是很好用的,能够帮助你解决大部分的内存泄露问题。所以还是推荐大家直接使用ARC,尽量不要使用mrc。

    参考文献

    1. Transitioning to ARC Release Notes
    2. iOS应用开发:什么是ARC?
    3. Blocks, Operations, and Retain Cycles
    4. iOS7.0 使用ARC
    5. block使用小结、在arc中使用block、如何防止循环引用
    6. IOS中关于NSTimer使用知多少
    7. 正确使用Block避免Cycle Retain和Crash
  • 相关阅读:
    xtoi (Hex to Integer) C function Nanoseconds Network
    Learning Scrapy | 王晨的博客
    Facebook搜索项目多名工程师均来自Google
    Beyond the C++ Standard Library: An Introduction to Boost: Björn Karlsson: 9780321133540: Amazon.com: Books
    归并排序 详解
    NewsFeed 3.0 发布,移植到 Python 3 开源中国 OSChina.NET
    对于拷贝构造函数和赋值构造函数的理解
    python 的os.fork()
    Install C++ Boost on Ubuntu
    石川的blog ,注意
  • 原文地址:https://www.cnblogs.com/jiang-xiao-yan/p/5362105.html
Copyright © 2011-2022 走看看