zoukankan      html  css  js  c++  java
  • 【iOS】自动引用计数 (循环引用)

    历史版本

    ARC(Automatic Reference Counting,自动引用计数)极大地减少了Cocoa开发中的常见编程错误:retainrelease不匹配。ARC并不会消除对retainrelease的调用,而是把这项原本大都属于开发者的工作移交给了编译器。这样做的好处是显而易见的,但是必须知道retainrelease是仍然在使用的。ARC并不等同垃圾回收。思考下面这段代码,它对一个实例变量赋值:

    1
    2
    3
    @property (nonatomic, readwrite, strong) NSString *title;
    ...
    _title = [NSString stringWithFormat:@"Title"];
    

    在非ARC环境中,_title没有被保留过,那么赋给它的NSString就是autorelease的,会在事件循环结束时被释放。之后如果再次访问_title,程序就会崩溃。这类错误极其常见,调试起来会异常困难。另外,如果_title之前已经有一个值,那旧的值就会因为没有被释放掉而引起内存泄漏。

    使用ARC,编译器会在合适的位置自动插入一些代码,下面的代码与前面的代码是等同的:

    1
    2
    3
    4
    id oldTitle = _title;
    _title = [NSString stringWithFormat:@"Title"];
    [_title retain];
    [oldTitle release];
    

    retainrelease仍然会被调用,所以有一些开销,在release的时候可能还会调用dealloc方法。这段代码与程序员手动调用retainrelease的代码在运行结果上是完全一致的。垃圾回收机制是在运行时起作用的,会影响运行效率,而ARC是在编译时插入内存管理代码,不影响运行时效率,因此内存回收比垃圾回收时的效率要高,能够提升系统性能。正如其他一些编译器优化方式一样,这种编译器可以自由地以多种方式优化内存管理,而让程序员手动去做这些工作是不现实的。在多数情况下,使用ARC生成内存管理代码的程序比程序员手工添加内存管理代码的对等程序运行更快!

    ARC不是垃圾回收,尤其是它不能像Snow Leopard中的垃圾回收机制那样处理循环引用(保留)。图3-1中的对象A与对象B之间存在循环保留。

    图3-1 循环保留

    在Snow Leopard的垃圾回收机制下,如果从“外部对象”到“对象A”的引用链接中断了,对象A和对象B都会被销毁,因为它们从程序中孤立出去了。而在ARC中,对象A和对象B都不会被销毁,因为它们的引用计数都大于0。因此,在iOS开发中,必须要做好对强引用(strong reference)的跟踪管理以免出现循环引用。

    属性关系有两种主要类型:strongweak,相当于非ARC环境里的retainassign。只要存在一个强引用,对象就会一直存在,不会被销毁。强引用类似于C++中的shared_ptr,只不过管理引用计数的代码是在编译时生成的,而shared_ptr是在运行时通过操作符重载确定的。

    Objective-C中一直存在循环引用的问题,但在实际应用中很少出现循环引用。对于过去那些使用assign属性的地方,在ARC环境中要使用weak代替。大部分引用循环是由委托(delegate)引起的,所以应该总是把delegate属性声明为weak。当引用的对象被销毁之后,weak引用会被自动置为nil,与assign相比这是一个巨大的进步,因为assign可以指向被释放掉的内存,导致程序崩溃。


    在ARC出现以前,合成属性(synthesized property)的默认存储类型是assign;而在ARC中,默认的存储类型是strong。这在进行代码转换时可能会造成一些困惑。建议为所有属性显式指明存储类型。 循环保留产生的另一个主要原因是块,其介绍详见第23章。


    将代码转换到ARC的时候,要注意以下两点。

    •   不要再使用retainreleaseautorelease,可以直接将这些代码删除。ARC在编译时会自动在合适的位置插入内存管理代码。
    •   如果dealloc方法只是用于释放实例变量,那么dealloc方法也没有存在的必要了,可以直接删掉。在任何情况下都不需要再使用release,ARC会自动处理好这些事情。如果仍然需要使用dealloc来做一些其他的事情(比如移除KVO观察),记得不要再调用[super dealloc],否则编译器会给出错误信息。

    正如前面所说的,ARC并不是垃圾回收,它是编译器的一种功能,可以在代码中合适的位置自动插入调用retainrelease的语句。这也意味着,如果现存的使用手动内存管理方式的代码很好地遵守了命名约定,那么这些代码跟ARC就是完全互通的。举例来说,如果调用了一个名为copySomething的方法,ARC会认为这个方法会对返回对象的引用计数加1,如果有必要,ARC会在合适的位置插入一个release。引用计数的加1操作既可以在ARC代码中进行,也可以在某段手动进行内存管理的代码中进行,这对于ARC来说是无所谓的。

    但是,如果没有遵守Cocoa命名约定,这种情况下就会出错。比如有一个名为copyRight的方法,它以autorelease的形式返回一个版权信息的字符串,这种情况下ARC的行为取决于调用代码和被调用代码是否都是使用ARC进行编译的。

    方法名是以copy开头的,因此ARC认为copyRight方法会对返回对象的引用计数加1。如果copyRight方法的代码与调用copyRight方法的代码都是使用ARC编译的,ARC就会在copyRight方法代码中添加一个retain语句,同时在调用copyRight方法的代码中添加一个release语句,这种情况下程序依然会正常工作。虽然对执行效率有一点点影响,但是程序不会崩溃,也不会造成内存泄漏。

    然而,如果调用copyRight方法的代码是使用ARC编译的(ARC就会在代码后边添加一个release语句),而copyRight方法代码没有使用ARC编译的话(方法体中就没有retain语句),程序就会崩溃。反过来,如果copyRight方法代码使用ARC编译(ARC就会在方法代码中添加一个retain语句),而调用代码没有使用ARC编译的话(调用代码中就不会有release语句),代码就会造成内存泄漏。

    最好的解决办法就是遵守Cocoa的命名约定。在这个例子中,如果将方法名改为copyright,就可以完全避免这种问题。ARC根据驼峰式大小写方式对方法的名称进行单词分隔,然后使用相应的内存管理规则。

    如果对一个错误命名的方法进行重命名是不现实的,那么还可以在方法声明中使用NS_RETURNS_RETAINED或者NS_RETURNS_NOT_RETAINED修饰符来告诉编译器应该使用哪种内存管理规则。这些修饰符是在NSObjCRuntime.h中定义的。

    为了让编译器能够恰当地添加retainrelease语句,编写ARC代码时需要注意以下四点。

    • 不要调用retainreleaseautorelease。直接将这些代码删除,这是最简单的规则。另外,也不可以覆盖这几个方法(永远不要试图去覆盖这几个方法)。如果你想实现Singleton模式,请阅读第4章,了解如何在不覆盖这些方法的情况下实现Singleton模式。

    • C语言结构体中不要有对象指针。这种情况很少出现。但是如果C语言结构体中确实有一个对象,要么把它存储到另一个对象中,要么就把它转换成void*类型(下一条规则会介绍如何转换到void*)。可以在任何时候通过调用free方法销毁一个C语言结构体,这会影响到对结构体中对象的自动跟踪。

    • idvoid*类型只能通过桥接转换进行转换。在使用了Core Foundation的代码中会有很多这种情况。第27章会详细介绍桥接转换及如何结合ARC来使用。

    • 不要使用NSAutoreleasePool。不要手动创建自己的自动释放池,直接把需要自己的池的代码放入@autoreleasepool{}代码块中即可。如果你有一些特殊的代码用于控制自动释放池的释放,那些代码实际上很可能是不必要的。@autoreleasepoolNSAutoreleasePool快了大约20倍。

    对于大部分代码来说,只要遵守这些规则就不会有任何问题。在Xcode中有一个小工具可以替你完成大部分代码转换的工作,它位于Edit → Refactor → Convert to Objective-C ARC。

    ARC应该是Objective-C中继自动释放池之后最重要的改进了,应该尽可能地使用。如果不能将代码全部转换到ARC,就尽可能地转换。使用ARC编译的程序速度更快,bug更少,也比手动内存管理代码更容易编写。现在就开始使用ARC吧!

  • 相关阅读:
    使用 OpenSmtp.dll 发送邮件 (记录) 西安
    国庆假期加班头疼 西安
    asp.net 下 使用 showModalDialog 模式窗口 (记录) 西安
    严重声讨 西安
    牙痛,医生说我这是根尖周炎,有点郁闷
    Google域名被国内某商抢注 竟只得重金去赎
    Windows自带的一个罕为人知的无敌命令
    在CSS中使用继承
    删除字符串最后一个字符的几种方法
    如何在一个RowFilter过的dataview中增加一行
  • 原文地址:https://www.cnblogs.com/DamonTang/p/3378131.html
Copyright © 2011-2022 走看看