PDF版下载:http://download.csdn.net/detail/cuibo1123/7443125 

 

 

    在Objective-C中,内存的引用计数一直是一个让人比较头疼的问题。尤其是当引用计数涉及到arc、blocks等等的时候。似乎ARC的出现只是让我们解放了双手,由于底层实现依然依赖引用计数,所以开启ARC后,只有对引用计数机制更加了解,才能避免Cycle Retain、Crash等问题的出现。

但是由于使用ARC可以显著提高编码效率,所以建议尽量启用arc,本文内容也将以arc为主,所有测试等如未说明均表示开启arc。

oc中内存的管理主要依赖引用计数,而对引用计数的影响又依赖修饰属性(暂且这么称呼),oc中常用的修饰属性如下:

属性

(1)修饰属性(使用@property定义时)

读写控制:

readwrite:可读可写,会生成getter和setter方法。

readonly:只读,只会生成getter方法,不会生成setter方法。

引用方式:

    copy:拷贝,复制一个对象并创建strong关联,引用计数为1 ,原来对象计数不变。

    assign:赋值,不涉及引用计数的变化,弱引用。ARC中对象不能使用assign,但原始类型(BOOL、int、float)仍然可以使用。

    retain:持有,对原对象引用计数加1,强引用。ARC中使用strong。

    weak:赋值(ARC),比assign多了一个功能,对象释放后把指针置为nil,避免了野指针。

    strong:持有(ARC),等同于retain。

线程安全:

    nonatomic:非原子操作,不加同步,多线程访问可提高性能,但不是线程安全的。

    atomic:原子操作,与nonatomic相反。

(2)修饰变量(修饰不使用@property定义时,比如函数内的局部变量)

    __strong:是缺省的关键词,强引用。

    __weak:声明了一个可以自动置nil的弱引用(ARC中)。

    __unsafe_unretained:声明一个弱引用,但是不会自动nil化(只有iOS 4 才应该使用)。

    __autoreleasing:用来修饰一个函数的参数,这个参数会在函数返回的时候被自动释放(类似autorelease)。

(3)默认的引用计数:

    alloc:分配对象,分配后引用计数为1。

    autorelease:对象引用计数减1,但如果为0不马上释放,等待最近一个pool时释放。

使用ARC

ARC,全称叫AutomaticReference Counting,该机制从ios5开始开始导入。简单地说,就是代码中自动加入了retain/release。所以,其底层机制还是引用计数,所以掌握引用计数对内存管理依旧非常非常重要,我甚至觉得使用arc的前提就是充分了解引用计数机制,否则几乎每天都要和Cycle Retain、Crash做斗争。

在你打开ARC时,你是不能使用retainrelease autorelease 操作的,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了,但是你需要在对象属性上使用weak 和strong, 其中strong就相当于retain属性,而weak相当于assign,基础类型还是使用assign。

(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。先看代码:

  1. //MyClassDelegate协议
  2. @protocol MyClassDelegate <NSObject>
  3. - (void)myClassOnSomeEvent:(MyClass*)myClass;
  4. @end
  5. //MyClass类
  6. @interface MyClass
  7. @property (weak,nonatomic)id<MyClassDelegate> delegate;   // (1)这里使用weak
  8. @end
  9. @interface myViewController
  10. //在myViewController中创建一个MyClass
  11. @property (strong,nonatomic)MyClass *myClass;
  12. @end
  13. @implementation myViewController
  14. - (void)someAction
  15. {
  16. myClass = [[MyClass alloc]init];     // (2)
  17. myClass.delegate = self;           // (3)
  18. ....
  19. }
  20. @end

在myViewController中,

执行myClass = [[MyClassalloc] init]; //(2),此时myViewController将会持有一个MyClass的引用。

执行myClass.delegate = self;//(3)时,myClass也会引用myViewController。

1、当myClass.delegate使用weak时,

不会出现互相引用问题,也不会出现crash(引用被释放)问题,

引用关系如下(假设myViewController由Somebody持有):

上图引用关系分析:

第一行:

Somebody创建(持有)myViewController,所以myViewController的引用计数为1。

myViewController持有myClass,所以myClass的引用计数为1。

myClass通过成员delegate引用myViewController,但使用weak弱引用,所以引用计数不受影响。

第二行:

如果Somebody释放myViewController,则myViewController引用计数减1,变成0,此时myViewController自己将会被释放(引用计数是0),并减少所持有对象(myClass)的引用计数。

第三行:

myclass引用计数为0,被释放,由于是delegate是weak属性的,所以delegate将自动被设置为空。

2、当myClass.delegate使用strong时,

会出现相互引用问题,导致对象无法被释放。

引用关系如下:

第一行:

Somebody创建(持有)myViewController,所以myViewController的引用计数为1。

myViewController持有myClass,所以myClass的引用计数为1。

myClass通过成员delegate引用myViewController,但使用strong引用,所以引用计数加1,变成2。

第二行:

如果Somebody释放myViewController,则myViewController引用计数减1,变成1,此时myViewController不会被释放,myclass引用计数为1,也不会被释放,这样就造成了互相引用问题。

而且没有外部对象在引用myViewController或myclass,造成了myViewController和myclass无法被释放。

3、当myClass.delegate使用weak时,如果有另一个Somebody同时持有了myClass,

由于weak可以自动置nil,所以不会出现crash(引用被释放)问题,

则引用关系如下:

第一行:

Somebody创建(持有)myViewController,所以myViewController的引用计数为1。

myViewController持有myClass,所以myClass的引用计数为1。

myClass通过成员delegate引用myViewController,但使用weak弱引用,所以引用计数不受影响。

第二行:

新的Somebody持有同一个myClass,导致myClass引用计数加1,变成2。

第三行:

如果Somebody释放myViewController,则myViewController引用计数减1,变成0,此时myViewController自己将会被释放(引用计数是0),并减少所持有对象(myClass)的引用计数。使myClass引用计数减1,变成1。由于myClass引用myViewController的delegate是weak属性的,所以delegate将自动被设置为空,不会出现crash(引用被释放)问题。

注意,如果myClass引用myViewController的delegate是assign的话,则delegate不会被自动设置为空,将导致delegate再次调用myViewController时出错(myViewController已经释放了)。

第四行:

Somebody正常持有myClass,如果此时Somebody释放myClass,则myClass引用计数减1并释放,不会出现任何问题。

关于block和引用计数

(1)修饰block

如果需要block在它被声明的作用域被销毁后继续使用的话,你就需要做一份拷贝。拷贝会把block移到堆里面。所以,使用@property时设置通常如下:

@property(copy, nonatomic)void(^block)(void);

 

(2)retain cycle的问题

block在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在block块内使用该只读拷贝。所以在使用block过程中,经常会遇到retain cycle的问题,例如:

  1. - (void)dealloc
  2. {
  3. [[NSNotificationCenter defaultCenter] removeObserver:_observer];
  4. }
  5. - (void)loadView
  6. {
  7. [super loadView];
  8. _observer = [[NSNotificationCenterdefaultCenter] addObserverForName:@"testKey"
  9. object:nil
  10. queue:nil
  11. usingBlock:^(NSNotification*note)
  12. {
  13. [sselfdismissModalViewControllerAnimated:YES];
  14. }];
  15. }

在block中用到了self,self会被block retain,而_observer会copy一份该block,就是说_observer间接持有self,同时当前的self也会retain_observer,最终导致self持有_observer,_observer持有self,形成retaincycle。

对于在block中的retain cycle,在2011 WWDC Session #322 (Objective-C Advancements in Depth)有一个解决方案weak-strong dance,其实现如下:

  1. - (void)dealloc
  2. {
  3. [[NSNotificationCenter defaultCenter] removeObserver:_observer];
  4. }
  5. - (void)loadView
  6. {
  7. [super loadView];
  8. __weakTestViewController *wself =self;
  9. _observer =[[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
  10. object:nil
  11. queue:nil
  12. usingBlock:^(NSNotification *note)
  13. {
  14. TestViewController *sself = wself;
  15. [sselfdismissModalViewControllerAnimated:YES];
  16. }];
  17. }

在block中使用self之前先用一个__weak变量引用self,导致block不会retain self,打破retain cycle,然后在block中使用wself之前先用__strong类型变量引用wself,以确保使用过程中不会dealloc。简而言之就是推迟对self的retain,在使用时才进行retain。

(3)return一个block

返回一个block时,ARC会自动将block加上autorelease,所以需要注意,如果执行过程中不能接受在runloop接受后才释放block,就需要自己加入@autoreleasepool块,但是测试发现64位iOS/mac时,系统会自动在使用结束后立即释放,32位则要等到runloop结束。

  1. - (void)test
  2. {
  3. //@autoreleasepool{
  4. AutoTest *a = [AutoTestsAutoTest];
  5. NSLog(@“1”);
  6. a = nil;
  7. NSLog(@"2");
  8. a = [[AutoTest alloc] init];
  9. //}
  10. NSLog(@"3");
  11. }
  12. - (IBAction)ok:(id)sender
  13. {
  14. [self test];
  15. NSLog(@"4");
  16. }

//执行结果

1释放23释放4   64位

123释放4释放   32位

12释放释放34   32位+@autoreleasepool

(4)block作为参数

block作为参数时,如果使用范围超过了block的作用域(比如异步时,或者将block传递给其他对象等等),则需要copy此block,copy建议在使用此block的方法内实现(谁使用,谁管理),而不是在传递参数时copy。注意,block过一个strong类型的指针时,会自动copy。经过copy过的block会从栈空间移动到堆上,并且,copy一个已经在堆上的block时,此block不会受影响。

*如果想了解block的内存管理原则,建议查阅相关文档,了解一下block的内部实现方式。Block的内存管理很难用简单的几句话来描述。

原创:http://www.xoneday.com

 

objective-c启用ARC时的内存管理 (循环引用)的更多相关文章

  1. objective-c启用ARC时的内存管理

    PDF版下载:http://download.csdn.net/detail/cuibo1123/7443125      在objective-c中,内存的引用计数一直是一个让人比較头疼的问题.尤其 ...

  2. 【转】iOS夯实:ARC时代的内存管理

    iOS夯实:ARC时代的内存管理 什么是ARC Automatic Reference Counting (ARC) is a compiler feature that provides autom ...

  3. iOS夯实:ARC时代的内存管理

    iOS夯实:ARC时代的内存管理 文章转自 ARC时代的内存管理 什么是ARC Automatic Reference Counting (ARC) is a compiler feature tha ...

  4. ARC下的内存管理

    1.ARC下单对象内存管理 局部变量释放对象随之被释放 int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = ...

  5. Objective-C内存管理之-引用计数

    本文会继续深入学习OC内存管理,内容主要参考iOS高级编程,Objective-C基础教程,疯狂iOS讲义,是我学习内存管理的笔记 内存管理 1 内存管理的基本概念 1.1 Objective-C中的 ...

  6. iOS的内存管理和引用计数规则、Block的用法以及三种形式(stack、malloc、global)

    学习内容 iOS的内存管理和引用计数规则 内存管理的思考方式 自己生成的对象自己持有 非自己生成的对象自己也能持有 自己持有的对象不需要时释放 非自己持有的对象不能释放 ARC有效时,id类型和对象类 ...

  7. ARC时代的内存管理

    什么是ARC Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory manag ...

  8. iOS- 非ARC的项目内存管理细节详解(实战)

    1.前言 接上文:iOS- 如何将非ARC的项目转换成ARC项目(实战) 2.内存管理时相关的配置 当我们把将非ARC的内存管理都管理好后,发现在做有些操作的时候内存还是在一直的缓慢增加 比如做一个最 ...

  9. ARC机制集合内存管理

    // //  main.m //  13-ARC机制集合内存管理 // //  Created by apple on 14-3-21. //  Copyright (c) 2014年 apple. ...

随机推荐

  1. How can I add a site title refiner

    一篇非常好的博客,收藏一下 https://sharepoint.stackexchange.com/questions/109409/how-can-i-add-a-site-title-refin ...

  2. Ubuntu安装使用SS客户端上网

    1.安装shadowsocks sudo apt-get install python-pip pip install shadowsocks 以上代码用root执行效果较好 查找shadowsock ...

  3. Use Multiple log4net Outputs from One Application

    Introduction This is an article simply to demonstrate how to use several output log files depending ...

  4. Java读取Unicode文件(UTF-8等)时碰到的BOM首字符问题,及处理方法

    转载:https://blog.csdn.net/clementad/article/details/47168573 2015-18-01修改:增加 apache commons io 处理方法. ...

  5. 腾讯企业邮箱设置发送邮件的配置(针对smtp)

    QQ邮箱也是如下配置,不过需要进行开启smtp

  6. 【Android】解析Android的路径

    目录结构: contents structure [+] 内部路径 外部路径 Assets目录 Android中的路径主要分为内部路径和外部路径. 一.内部路径 常见的内部存储路径有: File f1 ...

  7. 如何查看Apache的连接数和当前连接数

    查看Apache的连接数和当前的连接数以及IP访问次数,下面有个不错的示例,大家可以参考下,希望对大家解决问题有所帮助 查看了连接数和当前的连接数 复制代码 代码如下: netstat -ant | ...

  8. arcgis server瓦片行列号计算

    2013-08-01 arcgis server切图时会设置一个原点坐标,如果是经纬度坐标一般默认(x0,y0)=(-400,400)作为切图的起始点,即从这开始算行列号.假设切图的地图某点坐标为x, ...

  9. syslog之三:建立Windows下面的syslog日志服务器

    目录: <syslog之一:Linux syslog日志系统详解> <syslog之二:syslog协议及rsyslog服务全解析> <syslog之三:建立Window ...

  10. 译:5.RabbitMQ Java Client 之 Topics (主题)

    在 上篇博文 译:4.RabbitMQ 之Routing(路由) 中,我们改进了日志系统. 我们使用的是direct(直接交换),而不是使用只能进行虚拟广播的 fanout(扇出交换) ,并且有可能选 ...