以下是关于内存管理的学习笔记:引用计数与ARC。

iOS5以前自动引用计数(ARC)是在MacOS X 10.7与iOS 5中引入一项新技术,用于代替之前的手工引用计数MRC(Manual Reference Counting)管理Objective-C中的对象【官方也叫MRR(Manual Retain Release)】。如今,ARC下的iOS项目几乎把所有内存管理事宜都交给编译器来决定,而开发者只需专注于业务逻辑。

但是,对于iOS开发来说,内存管理是个很重要的概念,如果先要写出内存使用效率高而又没有bug的代码,就得掌握其内存管理模型的细节。

一、引用计数

1.与内存管理的关系?

在Objective-C内存管理中,每个对象都有属于自己的计数器:如果想让某个对象继续存活(例如想对该对象进行引用),就递增它的引用计数;当用完它之后,就递减该计数;当没人引用该对象,它的计数变为0之后,系统就把它销毁。

这个,就是引用计数在其中充当的角色:用于表示当前有多少个对象想令此对象继续存活程序中;

2.引用计数的介绍:

引用计数(Reference Count),也叫保留计数(retain count),表示对象被引用的次数。一个简单而有效的管理对象生命周期的方式。

3.引用计数的工作原理:

  1. 当我们创建(alloc)一个新对象A的时候,它的引用计数从零变为 1;
  2. 当有一个指针指向这个对象A,也就是某对象想通过引用保留(retain)该对象A时,引用计数加 1;
  3. 当某个指针/对象不再指向这个对象A,也就是释放(release)该引用,我们将其引用计数减 1;
  4. 当对象A的引用计数变为 0 时,说明这个对象不再被任何指针指向(引用)了,这个时候我们就可以对象A销毁,所占内存将被回收且所有指向该对象的引用也都变得无效了。系统也会将其占用的内存标记为“可重用”(reuse);

流程参考图如下:

(图片表格取自《编写高质量iOS与OS X代码的52个有效方法》一书)

4.操作引用计数的方法:

A.以下是NSObject协议中声明的3个用于操作计数器的方法:
  • retain : 保留。保留计数+1;
  • release : 释放。保留计数 -1;
  • autorelease :稍后(清理“自动释放池”时),再递减保留计数,所以作用是延迟对象的release;
B.dealloc方法:另外,当计数为0的时候对象会自动调用dealloc。而我们可以在dealloc方法做的,就是释放指向其他对象的引用,以及取消已经订阅的KVO、通知;(自己不能调用dealloc方法,因为运行期系统会在恰当的时候调用它,而且一旦调用dealloc方法,对象不再有效,即使后续方法再次调用retain。)

所以,调用release后会有2种情况:

调用前计数>1,计数减1;

调用前计数<1,对象内存被回收;

C.retainCount:获取引用计数的方法。

Eg: [object retainCount]; //得到object的引用计数

retain、release、autorelease详解:

retain作用:

调用后计数+1,保留对象操作。但是当对象被销毁、内存被回收的时候,即使使用retain也不再有效;

autorelease作用:

autorelease不立即释放,而是注册到autoreleasepool(自动释放池)中,等到pool结束时释放池再自动调用release进行释放工作。

autorelease看上去很像ARC,但是实际上更类似C语言中的自动变量(局部变量),当某自动变量超出其作用域(例如大括号),该自动变量将被自动废弃,而autorelease中对象实例的release方法会被调用;[与C不同的是,开发者可以设定变量的作用域。]

释放时间:每个Runloop中都创建一个Autorelease pool(自动释放池),每一次的Autorelease,系统都会把该Object放入了当前的Autorelease pool中,并在Runloop的末尾进行释放,而当该pool被释放时,该pool中的所有Object会被调用Release。 所以,一般情况下,每个接受autorelease消息的对象,都会在下个Runloop开始前被释放。

例如可用以下场景:(需要从ARC改为使用手动管理的可以做如下的设置: 在Targets的Build Phases选项下Compile Sources下选择要不使用ARC编译的文件,双击它,输入-fno-objc-arc即可使用MRC手工管理内存方式;)

-(NSString *)getSting
{
NSString *str = [[NSString alloc]initWithFormat:@"I am Str"];
return [str autorelease];
}

自动释放池中的释放操作会等到下一次时间循环时才会执行,所以调用以下:

NSString *str = [self getSting];
NSLog(@"%@",str);

返回的str对象得以保留,延迟释放。因此可以无需再NSLog语句之前执行保留操作,就可以将返回的str对象输出。

所以可见autorelease的作用是能延长对象的生命期。使其在跨越方法调用边界后依然可以存活一段时间。

release作用:

release会立即执行释放操作,使得计减1;

有这样一种情况:当某对象object的引用计数为1的时候,调用“[object release];”,此时如果再调用NSLog方法输出object的话,可能程序就会崩溃,当然只是有可能,因为对象所占内存在“解除分配(deallocated)”之后,只是放回“可用内存池(avaiable pool)”,但是如果执行NSLog时,尚未覆写对象内存,那么该对象依然有效,所以程序有可能不会崩溃,由此可见,因过早地释放对象而导致的bug很难调试。

为避免这种情况,一般调用完对象之后都会清空指针:"object = nil",这样就能保证不会出现指向无效对象的指针,也就是悬挂指针(dangling pointer);

悬挂指针:指向无效对象的指针。

那么,向已经释放(dealloc)的对象发送消息,retainCount会是多少?

原则是不可以这么做。因为该对象的内存已经被回收,而我们向一个已经被回收的对象发了一个 retainCount 消息,所以它的输出结果应该是不确定的,例如为减少一次内存的写操作,不将这个值从 1 变成 0,所以很大可能输出1。例如下面这种情况:

Person *person = [[Person alloc] init]; //此时,计数 = 1   
[person retain];  //计数 = 2   
[person release]; //计数 = 1   
[person release]; //很可能计数 = 1;  

虽然第四行代码把计数1release了一次,原理上person对象的计数会变成0,但是实际上为了优化对象的释放行为,提高系统的工作效率,在retainCount为1时release系统会直接把对象回收,而不再为它的计数递减为0,所以一个对象的retainCount值有可能永远不为0;

因此,不管是否为ARC的开发环境中,也不推荐使用retainCount来做为一个对象是否存在于内存之中的依据。


二、ARC

1.背景:

ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。

即使2014 年的 WWDC 大会上推出的Swift 语言,该语言仍然使用 ARC 技术作为其管理方式。

2.ARC是什么?

需要注意的是,ARC并不是GC(Garbage Collection 垃圾回收器),它只是一种代码静态分析(Static Analyzer)工具,背后的原理是依赖编译器的静态分析能力,通过在编译时找出合理的插入引用计数管理代码,从而提高iOS开发人员的开发效率。

Apple的文档里是这么定义ARC的:

“自动引用计数(ARC)是一个编译器级的功能,它能简化Cocoa应用中对象生命周期管理(内存管理)的流程。”

3.ARC在做什么?

在编译阶段,编译器将在项目代码中自动为分配对象插入retain、release和autorelease,且插入的代码不可见。

但是,需要注意的是,ARC模式下引用计数规则还起作用,只是编译器会为开发者分担大部分的内存管理工作,除了插入上述代码,还有一部分优化以及分析内存的管理工作。

作用:

  • a.降低内存泄露等风险 ;
  • b.减少代码工作量,使开发者只需专注于业务逻辑;

4.ARC具体为引用计数做了哪些工作?

编译阶段自动添加代码:

编译器会在编译阶段以恰当的时间与地方给我们填上原本需要手写的retain、release、autorelease等内存管理代码,所以ARC并非运行时的特性,也不是如java中的GC运行时的垃圾回收系统;因此,我们也可以知道,ARC其实是处于编译器的特性。

例如:

-(void)setup
{
_person = [person new];
}

在手工管理内存的环境下,_person是不会自动保留其值,而在ARC下编译,其代码会变成:

-(void)setup
{
person *tmp = [person new];
_person = [tmp retain];
[tmp release];
}

当然,在开发工作中,retain和release对于开发人员来说都可以省去,由ARC系统自动补全,达到同样的效果。

但实际上,ARC系统在自动调用这些方法时,并不通过普通的Objective-C消息派发控制,而是直接调用底层C语言的方法:

比如retain,ARC在分析到某处需要调用保留操作的地方,调用了与retain等价的底层函数 objc_retain,所以这也是ARC下不能覆写retain、release或者autorelease的原因,因为这些方法在ARC从来不会被直接调用。

运行期组件的优化:

ARC是编译器的特性,但也包含了运行期组件,所执行的优化很有意义。

例子:

person工厂方法personWithName可以得到一个person对象,在这里调用并赋值给person的一个实例_one

_one = [person personWithName:@"name"];

可能会出现这种情况:

在personWithName方法中,返回对象给_one之前,为其调用了一次autorelease方法。

由于实例变量是个强引用,所以编译器会在设置其值的时候还需要执行一次保留操作。

person *tmp = [person personWithName:@"name"]; //在personWithName方法返回前已有调用一次autorelease方法进行保留操作;
_one = [tmp retain];

很明显,autorelease与紧跟其后的retain是重复的。为提升性能,可以将二者删去,舍弃autorelease这个概念,并且规定返回对象的技术都比期望值多1,但是为了向后兼容非ARC等情况,ARC采取另外一种方式:

ARC可以在运行期检测到这一对多余的操作。

  1. 返回对象时,不直接调用autorelease,改为调用objc_autoreleaseReturnValue,用来检测返回之后即将要执行的代码中,含有retain操作,则设置全局数据结构(此数据结构具体内容因处理器而异)中的一个标志位,而不执行autorelease操作。
  2. 同样,若方法返回一个自动释放对象,调用personWithName方法的代码段不执行retain,改为执行objc_retainAutoreleaseReturnValue函数。此函数检测刚才的那个标志位,若已经置位了,则不执行retain操作。

而,设置并检测标志位,要比调用autorelease和retain更快,这就使得这一情况的处理得到优化。

修改2个函数后优化完整结果如下: 【例子来自《编写高质量iOS与OS X代码的52个有效方法》一书P126】

我们可以通过两个函数的伪代码大致描述如下:

    

像是objc_autoreleaseReturnValue这个函数是如何检测方法调用者是否会立刻保留对象呢,这就要交给处理器来解决了。

由于必须查看原始机器码指令方可判断出这一点需要处理器来定。

所以,其实只有编译器的作者才能知道这里是如何实现此函数的。

ARC的安全性:

在编写属性的设置方法(setter)时,如果使用手工管理方式,可能会需要如下编写:

-(void)setObject:(id)object
{
[_object release];
_object = [object retain];
}

但是这样写会出现问题:如果说新值object和实例变量_object的值是相同的,而且只有当前实例变量对象还在引用这个值,那么设置方法中的释放操作会使得该值保留计数为0,系统将其回收,所以接下来的保留操作,将会令应用程序崩溃。

而在使用ARC的环境下,就不可能会发送这样的的“边界情况”了:

刚才的代码在ARC下可以这样写(当然,我们知道如果不需要覆写setter方法,也可以不编写此方法,直接使用"self.object = xxx"也可以安全地调用。):

-(void)setObject:(id)object
{
_object = object;
}

而且ARC会用一种安全的方式来设置:先保留新值,再释放旧值,最后设置实例变量。

在手工管理的情况下,我们需要特别注意这种"边缘情况",但是ARC下,我们就可以很轻松地编写这种代码了,而不用去考虑这种情况如何处理了。

总结:将内存管理交由编译器运行期组件来做,可以使代码得到多种优化,而上面是其中一种方式。

5.ARC下需要注意的规则

不能显式调用以下代码:

(NSZone:内存区)

不能再使用NSAutoreleasePool对象,ARC提供了@autoreleasepool块来代替它,这样更有效率;

关于dealloc:

  • 不能显式调用dealloc;
  • 不能再dealloc中调用【super dealloc】(非ARC下则需要调用.);
  • 不能在dealloc 中释放资源(非ARC下需要释放不同的对象);

6.所有权修饰符

oc编程中为了处理对象,可将变量类型定义为id类型或各种对象类型。使用这些限定符可以确切地声明对象变量和属性的生命周期;

所谓对象类型就是指向NSObject这样的oc类的指针,例如“NSObject *”。id类型用于隐藏对象类型的类名部分。相当于C语言中常用的“void *”;

ARC下,id类型和对象类型上必须附加所有权修饰符;

所有权修饰符一共有4种:

__strong:

强引用,可以引用别的对象为强引用,相当于retain的特性;表明变量持有alloc/new/copy/mutableCopy方法群创建的对象的强引用,强引用变量会在其作用域里被保留,在超出作用域后被释放,为默认的修饰符;

例如以下代码

id objc = [[NSObject alloc] init];

实际上已被附上所有权修饰符:

id __strong objc = [[NSObject alloc] init];

__weak:

使用__strong,有可能2个对象相互强引用或者1个对象对自身强引用则会发生循环引用(如下图,或者叫保留环),所以当对象在超出其生存周期后,本应被系统废弃却仍然被引用者所持有,所以造成内存泄露(应当废弃的对象在超出生命周期后,继续存在);

        

而当我们对可能会发送循环引用的对象进行__weak弱引用修饰,弱引用变量不会持有对象,且生成的对象会立刻释放,可避免循环引用,并且弱引用还有另外一个特点,若对象被系统回收,该弱引用变量将自动失效并且赋值为nil。

__unsafe_unretained: 不安全的所有权修饰符,ARC的内存管理是编译器的工作,而附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。与__weak作用一样,也可以避免循环引用;但是不同的是,__unsafe_unretained属性的变量不会将变量设置为nil,而是就处于于悬挂状态;

__autoreleasing:在ARC中使用“@autoreleasepool块”来取代“NSAutoreleasePool”类对象的生成,通过将对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法;

Other:ARC需要注意的事项?

1.过度使用 block 之后,无法解决循环引用问题。

2.遇到底层 Core Foundation 对象,需要自己手工管理它们的引用计数时,我们需转换关键字,作为桥接转换以解决 Core Foundation 对象与 Objective-C 对象相对转换的问题:

__bridge:使用__bridge标记可以在不修改相关对象的引用计数的情况下,将对象从Core Foundation框架数据类型转换为Foundation框架数据类型(反之亦然)。

__bridge_retained:会将相关对象的引用计数加 1,并且可以将Core Foundation框架数据类型对象转换为Foundation框架数据类型对象,并从ARC接管对象的所有权。

__bridge_transfer:可以将Foundation框架数据类型对象转换为Core Foundation框架数据类型对象,并且会将对象的所有权交给ARC管理,也就是说引用计数交由ARC管理;

总结:就推荐2本经典的书(估计很多人早就看完了

iOS-引用计数与ARC(转)的更多相关文章

  1. iOS开发--引用计数与ARC

    以下是关于内存管理的学习笔记:引用计数与ARC. iOS5以前自动引用计数(ARC)是在MacOS X 10.7与iOS 5中引入一项新技术,用于代替之前的手工引用计数MRC(Manual Refer ...

  2. iOS 引用计数

    一.简介 OC 在创建对象时,不会直接返回该对象,而是返回一个指向对象的指针. OC 在内存管理上采用了引用计数,它是一个简单而有效管理对象生命周期的方式.在对象内部保存一个用来表示被引用次数的数字, ...

  3. Swift 自己主动引用计数机制ARC

    Swift 使用自己主动引用计数(ARC)这一机制来跟踪和管理你的应用程序的内存.通常情况下,Swift 的内存管理机制会一直起着作用,你无须自己来考虑内存的管理.ARC 会在类的实例不再被使用时,自 ...

  4. Swift 自动引用计数(ARC)

    Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存 通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存. 但在有些时候我们还是需要在 ...

  5. iOS - 引用计数探讨

    <Objective-C 高级编程> 这本书有三个章节,我针对每一章节进行总结并加上适当的扩展分享给大家.可以从下面这张图来看一下这三篇的整体结构: 注意,这个结构并不和书中的结构一致,而 ...

  6. iOS项目转移到自动引用计数

    这里主要参考了Apple官方文档:Transitioning to ARC Release Notes 在支持iOS5的Xcode4中,创建项目会看到这样的选项: 这是iOS5的新特性,自动对象引用计 ...

  7. 自动引用计数(ARC)

    1.1什么是自动引用技术 顾名思义,自动引用计数(ARC, Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术.以下摘自苹果官方说明: 在Objectiv ...

  8. Swift5 语言指南(二十五) 自动引用计数(ARC)

    Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用情况.在大多数情况下,这意味着内存管理在Swift中“正常工作”,您不需要自己考虑内存管理.当不再需要这些实例时,ARC会自动释放类实 ...

  9. Swift2.1 语法指南——自动引用计数

    原档: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programm ...

随机推荐

  1. 爬虫--Scrapy-基于RedisSpider实现的分布式爬虫

    爬取网易新闻 需求:爬取的是基于文字的新闻数据(国内,国际,军事,航空) 先编写基于scrapycrawl 先创建工程 scrapy startproject 58Pro cd 58Pro 新建一个爬 ...

  2. Win2008R2配置WebDeploy(转)

    一.配置服务器 1.安装管理服务 2.点击管理服务进行配置 3.安装WebDeploy 3.1通过离线安装包方式安装: https://www.iis.net/downloads/microsoft/ ...

  3. Unicode UTF8 UTF16 urlencode base64

    Unicode:是一个字符集,每个字符对应一个唯一的unicode编码,一般是16位. UTF8是针对Unicode的编码方式,因为如果每个字符都用unicode的编码存储的话会很浪费空间,比如说as ...

  4. TCP连接异常断开检测(转)

    TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断 ...

  5. Linux命令:unzip

    语法: unzip [-Z] [-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir] 默认行为将zip文件中的内容全部解压缩到当前目录下. ...

  6. U3D的结构体堆分配栈分配

    ST ot;//分配在栈上 ST[] arrt = new ST[2];//分配在堆上,因为数组是引用

  7. Java IO流学习总结四:缓冲流-BufferedReader、BufferedWriter

    在上一篇文章中Java IO流学习总结三:缓冲流-BufferedInputStream.BufferedOutputStream介绍了缓冲流中的字节流,而这一篇着重介绍缓冲流中字符流Buffered ...

  8. 浅谈MySQL事务及隔离级别

    目录 1.什么是事务 2.事务的ACID属性 2-1.原子性(Atomicity) 2-2.一致性(Consistency) 2-3.隔离性(Isolation) 2-4.持久性(Durability ...

  9. JAVA仿百度分页

    最近在做一个仿百度网盘的网页小应用,找到了一个优雅简洁的分页插件,和百度搜索的分页很相似,对他进行了二次封装,拿出来跟大家分享下 插件源码 /** * This jQuery plugin displ ...

  10. 使用__slots__限制实例的属性

    1.给实例化的对象添加新的属性 看下面一段代码,然后给实例化的对象s添加或者修改属性 class Student(object): name='china' s = Student() s1=Stud ...