自动引用计数ARC

  • 时间:2017-10-30
  • 分类:IOS开发
  • 902 人浏览
[导读]1.什么是自动引用计数?顾明思义,自动引用计数ARC,Automatic Reference Counting是指内存管理中对引用采取自动计数的技术。在OC中采用ARC机制,让编译器来进行内存管理。

1.什么是自动引用计数?

顾明思义,自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术。

在OC中采用ARC机制,让编译器来进行内存管理。在新一代apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码,这在降低程序崩溃、内存泄露等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立刻释放那些不在被使用的对象。

1.2 内存管理/引用计数 1.2.1 概要

OC中的内存管理,也就是引用计数。可以用开关房间的等为例来说明引用计数的机制,比如:上班进入办公室需要照明,下班离开办公室不需要照明。

假设办公室里的照明设备只有一个。上班进入办公室的人需要照明,所以要把灯打开,而对于下班离开办公室的人来说,已经不需要照明了,所以要把灯关掉。若是很多人上下班,每个人都开灯或是关灯,那么办公室的情况又将如何呢?最早下班离开的人如果关了灯,办公室里还没有走的人都将处于一片黑暗当中。

解决这个问题的办法是使办公室在还有至少1人的情况下保持开灯状态,而在无人时保持关灯状态。

a.第一个人进入办公室,“需要照明的人数”加1,计数值从0变成了1,因此要开灯。

b.之后每当有人进入办公室,"需要照明的人数"就加1,计数值从1变成2.

c.每当有人下班离开办公室,"需要照明的人数"就减1。如计数值从2变成1.

d.最后一个人下班离开办公室时,"需要照明的人数"减1,计数值从1变成了0,因此要关灯。

对办公室照明设备所做的动作和对OC对象所做的动作的对比。

对照明设备所做的动作:开灯 - 需要照明 - 不需要照明 - 关灯

对OC对象所做的动作:  生成对象 - 持有对象 - 释放对象 - 废弃对象。

使用计数功能计算需要照明的人数,使办公室的照明得到了很好的管理。同样,使用引用计数功能,对象也就能够得到很好的管理,这就是OC的内存管理。

1.2.2 内存管理的思考方式

自己生成的对象,自己所持有。

非自己生成的对象,自己也能持有。

不在需要自己持有的对象时释放。

非自己持有的对象无法释放。

//  对应的方法

对象操作:生成并持有对象 - 持有对象 - 释放对象 - 废弃对象

OC方法  :alloc/new/copy/mutablecopy - retain - release - dealloc

这些有关OC内存管理的方法,实际上不包括在该语言中,而是包含在cocoa框架中,用于ios开发。cocoa框架中Foundation框架类库的NSObject类担负内存管理的职责。OC内存管理中的alloc/retain/release/dealloc方法分别指代NSObject类的alloc类方法、retain实例方法、release实例方法和dealloc实例方法。

自己生成对象,自己所持有

使用alloc new copy mutableCopy 开头的方法名意味着自己生成的对象只有自己持有。本文所说的“自己”固然对应前文提到的“对象的使用环境”,但将之理解为编程人员“自身”也是没错的。下面写出了自己生成并持有对象的代码,为生成并持有对象,我们使用alloc 方法。

copy 方法利用基于NSCopying方法约定,由各类实现的copyWithZone,方法生成并持有对象的副本。与copy方法类似,mutableCopy方法利用基于NSMutableCopying方法约定,由各类实现的mutableCopyWithZone方法生成并持有对象的副本。两者的区别在于,copy方法生成不可变更的对象,而mutableCopy方法生成可变更的对象。这类似于NSArray和NSMutableArray的差异。

allocMyObject newThatObject copyThis mutableCopyYourObject 方法也意味着自己生成并持有对象。

但是allocate newer copying mutableCopyed 并不属于同一类别的方法。

非自己生成的对象,自己也能持有 

用上述项目之外的方法取得的对象,即用alloc/new/copy/mutableCopy 以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。我们来使用NSMutableArray类的array类方法。

不需要自己持有的对象时释放

自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法。 

 如果要用某个方法生成对象,并将其返还给该方法的调用方,那么他的源代码又是什么样的呢?

 autorelease 提供这样的功能,使对象在超出指定的生存范围时能够自动并正确的释放(调用release方法)下面是release和autorelease的区别

无法释放非自己持有的对象

对于非alloc/new/copy/mutableCopy方法生成并持有的对象,或是用retain方法持有的对象,由于持有者是自己,所以在不需要该对象时需要将其释放。而由此之外所得到的对象绝对不能释放。倘若在应用程序中释放了非自己所持有的对象就会造成崩溃。例如自己生成并持有对象后,在释放完不再徐亚ode对象之后再次释放。

 如这些例子所示,释放非自己持有的对象会造成程序崩溃。因此绝对不要去释放非自己持有的对象。

1.2.3 alloc/retain/release/dealloc 实现

包含NSObject类的Foundation框架并没有公开,不过,Foundation框架使用的Core Foundation框架的源代码,以及通过调用NSObject类进行内存管理部分的源代码是公开的。但是,没有NSObject类的源代码,就很难了解NSObject类的内部实现细节,所以,我们首先使用开源软件GNUstep来说明。

GNUstep是Cocoa框架的互换框架,也就是说,GUNstep的源代码虽不能说和Cocoa实现完全相同,但是从使用者角度来看,两者的行为和实现方式是一样的,或者说非常相似,

 注:NSDefaultMallocZone,NSZoneMalloc等名称中包含的NSZone是什么呢?它是为了防止内存碎片化而引入的结构,对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高了内存管理的效率。

但是,如同苹果官方文档中所说,现在的运行时系统只是简单地忽略的区域的概念,运行时系统中的内存管理本身已经极具效率,使用区域来管理内存反而会引起内存使用效率低下以及源代码复杂化等问题。

下面是去掉NSZone后简化了的源代码:

 alloc类方法用struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置0后返回。

对象的引用计数可通过retainCount实例方法取得

1.2.4 苹果的实现

在NSObject 类的alloc类方法上设置断点,追踪程序的执行,以下列出了执行所调用的方法和函数。

+alloc

+allocWithZone

class_createInstance

calloc

alloc类方法首先调用allocWithZone类方法,这和GNUstep的实现相同,然后调用class_createInstance函数,最后通过调用calloc来分配内存块。这和前面的GNUstep的实现并无多大差异。

retainCount/retain/release实例方法又是怎么实现的呢?下面列出各个方法分别调用的方法阿和函数

-retainCount

_CFDoExternRefOperation

CFBasicHashGetCountOfKey

 

-retain

_CFDoexternRefOperation

CFBasicHashAddValue

 

-release 

_CFDoExternRefOperation

CFBasicHashRemoveValue

(CFBasicHashRemoveValue 返回0 时,-release调用dealloc)

各个方法都通过同一个调用了_CFDoExternRefOperation函数,调用了一系列名称相似的函数。如这些函数名的前缀"CF"所示,它们包含于Core Foundation框架源代码中,即是CFRuntime.c的_CFDoExternRefOperation函数。

1.2.5 autorelease

autorelease 就是自动释放,这看上去很像ARC,但实际上它更类似于C语言中自动变量(局部变量)的特性。

在C语言中,程序执行时,如果某自动变量超出其作用域,该自动变量将被自动废弃。

 

autorelease会像C语言的自动变量那样来对待对象实例。当超出其作用域(相当于变量作用域)时,对象实例的release实例方法被调用。另外,同C语言的自动变量不同的是,编程人员可以设定变量的作用域。

autorelease的具体使用方法如下:

(1)生成并持有NSAutoreleasePool 对象。

(2)调用已分配对象的autorelease实例方法

(3)废弃NSAutoreleasePool对象。

NSAutoreleasePool对象的生存周期相当于C语言变量的作用域。对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。

源代码如下:

 在Cocoa框架中,相当于程序主循环的NSRunLoop或者在其它程序可运行的地方,对NSAutoreleasePool对象进行生成,持有和废弃处理。因此,应用程序开发者不一定非得使用该对象进行开发工作。

尽管如此,但在大量产生autorelease的对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象。典型的例子就是读取大量图像的同时改变其尺寸。图像文件读入到NSData对象,并从中生成UIImage对象,改变该对象尺寸后生成新的UIImage对象,这种情况下,就会大量产生autorelease的对象。

在此情况下有必要在适当的地方,生成,持有或废弃Pool对象。

通常在使用OC,也就是Foundation框架时,无论调用哪一个对象的autorelease实例方法,实现上是调用的都是NSObject类的autorelease实例方法。但是对于NSAutoreleasePool类,atorelease实例方法已被该类重载,因此运行时就会出错。

1.3 ARC规则 1.3.1 概要

实际上“引用计数式”内存管理的本质部分在ARC中并没有改变,就像 “自动引用计数”这个名称表示的那样,ARC只是自动的帮助我们处理“引用计数”的相关部分。

对某个文件可选择使用或不使用ARC.

1.3.2 内存管理的思考方式

自己生成的对象,自己持有

非自己生成的对象,自己也能持有

自己持有的对象不再需要时释放

非自己持有的对象无法释放

1.3.3 所有权修饰符

OC编程中为了处理对象,可将变量类型定义为id类型或各种对象类型。

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

ARC有效时,id类型和对象类型通C语言其它类型不同,其类型上必须附加所有权修饰符。

__strong __weak __unsafe_unretained __autoreleasing

__strong 修饰符

__strong 修饰符是id类型和对象类型默认的所有权修饰符。也就是说,以下源代码中的id变量,实际上被附加了所有权修饰符。

id obj = [[NSObject alloc] init];

id 和对象类型在没有明确指定所有权修饰符时,默认为__strong修饰符。上面的代码与以下相同。

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

该源代码再ARC无效的时候又该如何表述呢?

 为了释放生成并持有的对象,增加了调用release方法的代码。该源代码进行的动作同先前ARC有效时的动作完全一样。

如此源代码所示,附有__strong修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。

如 "strong" 这个名称所示,__strong 修饰符表示对对象的"强引用"。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

下面关注一下源代码中关于对象的所有者的部分

 此处,对象的所有者和对象的生存周期是明确的,那么,在取得非自己生成并持有的对象时又会如何呢?

 当然,附有__strong 修饰符的变量之间可以相互赋值。

 __weak 修饰符

两个对象循环引用的例子。

// 循环引用容易发生内存泄露,所谓内存泄露就是应当废弃的对象在超出其生存周期后继续存在。此代码的本意是赋予变量test0的对象a和对象b在超出其变量作用域时被释放,即在对象不被任何变量持有的状态下予以废弃。但是,循环引用使得对象不能被再次废弃。

像下面这种情况,虽然只有一个对象,但在该对象持有其自身时,也会发生循环引用(自引用)

id test = [[Test alloc] init];

[test setObject:test];

怎么样才能避免循环引用呢?看到__strong修饰符就会意识到了,既然有strong,就应该有与之对应的weak,也就是说,使用__weak修饰符可以避免循环引用。

__weak修饰符与__strong修饰符相反,提供弱引用,弱引用不能持有对象实例。

 此源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量obj,即变量obj持有对象持有对象的弱引用。因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。编译器对此会给出警告。如果像下面这样,将对象赋值给附有__strong 修饰符的变量之后再赋值给附有__weak 修饰符的变量,就不会发生警告了。

 因为带__weak 修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放如果像下面这样将先前可能发生循环引用的类成员变量改成附有__weak修饰符的成员变量的话,该现象可以避免。

 __weak修饰符还有另一个优点,在持有某对象的弱引用的时候,若该对象被废弃,这个弱引用也会被置为nil

 像这样,使用__weak可以避免循环引用。

 

来源:本文为线上采编,如涉及作品内容、版权和其它问题,请及时与本网联系,我们将在第一时间删除!
标签: ARC 计数