Objective-C内存管理(一)

1.1 什么是自动引用计数####

顾名思义,自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术,以下是摘自苹果的官方说明。
在Objective-C中采用Automatic Reference Counting(ARC)机制,让编译器来进行内存管理。在新一代Apple LLVM编译中设置ARC为有效状态,就无需再次键入Retain或者Release代码,这在降低程序崩溃,内存泄漏等风险的同时,很大一定程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立刻释放那些不再被使用的对象。如此一来,应用程序将具有可预测性,且能流畅运行,速度也将大幅提升。
这些优点无疑具吸引力,但关于ARC技术,最重要的还是下面这一点
“在LLVM编译器中设置ARC为有效状态,无需再次键入retain或者release代码”
换言之,若满足以下条件,就无需手动输入retain或者release代码了。

1 使用Xcode4.2或者以上版本
2 使用LLVM编译器3.0或者以上版本
3 编译器选项中ARC设置为有效

在以上条件下编译源代码时 编译器将自动进行内存管理,这正是每个程序员梦寐以求的。在正式讲解精彩的ARC技术之前,我们先来了解一下,在此之前,程序员在代码中是如何手工进行内存管理的。

1.2 内存管理/引用计数

Objective-C中的内存管理,也就是引用计数管。可以用来开关房间的灯为例来说明引用计数的机制。
假设办公室的照明设备只有一个。上班进入办公室的人需要照明,所以要把灯打开。而对于下班离开办公室的人来说,已经不再需要照明了,所以要把灯关掉。若是很多人上下班,每个人开灯或是关灯,那么办公室的情况有将是一个什么样子呢?最早下班的人如果关了灯,办公室的剩下的人将会处于一片漆黑之中。
解决这一问题的办法是办公室在至少还有一个人的情况下,要保持一个开灯的状态,而在完全没有人的情况下,保持关灯状态。

最早进入办公室的人开灯
之后需要进入办公室的人需要照明
下班离开办公室的人不需要照明
最后离开办公室的人关灯(此时办公室已没有人需要照明)

为判断是否还有人在办公室里,这里导入计数功能来计算“需要照明的人数”。下面让我们这一功能是如何运作的吧。
(1)第一个人进入办公室,需要照明的人数+1.计数从0变成了1,因此要开灯
(2)之后当有人进入办公室的时候,需要照明的人数就+1。如计数从1变成2.
(3)每当有人下班离开办公室的时候,需要照明是人数就-1,如计数从2变成1.
(4)最后一个人下班离开办公室的时候,需要照明的人数-1.计数从1变成了0,因此要关灯。
这样就可以在不需要照明灯情况下 保持关灯的状态,办公室仅有的照明设备得到了很好的利用。
在Objective-C中,“对象”相当于办公室的照明设备,在现实世界中办公室的照明设备只有一个,但在ObjectIve-C的世界里,虽然计算机资源有限,但一台计算机可以同时处理多个对象。
此外”对象的使用环境“相当于上班进入办公室吧的人。虽然这里的”环境“有时候也指在运行中的程序代码,变量,变量作用域,对象等,但在概念上就是使用对象的环境。上班进入办公室的人对照明设备发出的动作,与ObjectIve-C中的对应关系是

对照明设备所做的动作 对Objective-C对象所做的动作
开灯 生成对象
需要照明 持有对象
不需要照明 释放对象
关灯 废弃对象

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

1.3 内存管理的思考方式

引用计数内存管理的思考方式。看到引用计数这个名称 我们便会不自觉的联想到“某处某物多多少少” 而将注意力放在计数上面。但其实,更贱客观,正确的思考方式是:
<自己生成的对象,自己持有
<非自己生成的对象,自己也能持有
<不再需要自己持有的对象时释放
<非自己持有的对象不能释放
引用计数式内存管理的思考方式仅此而已。按照这个思路,完全不必要考虑引用计数。
上文出现了“生成”,“持有”,“释放”三个词。而在Objective-C内存管理中还要加上“废弃”一词,这四个词频繁出现,而各个词语表示的Objective-C方法如下
对象操作 Objective-C方法
生成并持有对象 alloc/new/copy/mutableCopy等方法
持有对象 retain
释放对象 release
废弃 dealloc
这些有关Objective-C内存管理的方法,实际上不包括在Objective-C语言中,而是包含在Cocoa框架中用于OS X,iOS应用开发。Cocoa框架中Foundation框架类库的NSObject类担负内存管理的职责。Objective-C内存管理中的alloc/retain/release/dealloc方法分别只带NSobject类的alloc方法,retain方法,release方法和dealloc实例方法。
自己生成的对象,自己所持有
使用一下名称开头的方法名意味着自己生成的对象只有自己持有,在这里自己是指对象的使用环境

alloc

    /**
     自己生成并持有对象
     */
    id obj =[[NSObject alloc]init];

使用NSObject类的alloc方法就能自己生成并持有对象。指向生成并持有对象的指针被赋予变量obj

new

    /**
     自己生成并持有对象
     */
    id obj =[NSObject new];

copy >mutbleCopy
copy方法是基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。与copy方法类似mutableCopy方法是基于mutableCopying约定,由各类实现的mutableCopyWithZone:方法生成并持有对象,虽然是对象的副本,但是同alloc、new、方法一样,在“自己生成并持有对象”,这点上没有改变
另外,根据上述“使用一下名称开头的方法名”,下列名称也意味着自己生成并持有吧对象。
allocMyObject
newThatObject
copyThis
mutableCopyYourObject
非自己生成的对象,自己也能持有
用上述项目之外的方法取得的对象,即用alloc/new/copy/mutableCopy以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。我们来使用alloc/copy/new/mutableCopy以外的方法看看,这里试用NSMutableArray的array方法

    /**
     取得非自己生成并持有对象
     */
    id obj =[NSMutableArray array];

源代码中NSMutableArray类对象被赋予变量obj 但变量obj自己并不持有该对象,使用retain方法可以持有对象

    /**
     取得非自己生成并持有对象
     */
    id obj =[NSMutableArray array];
    /**
     *  取得对象存在,但自己不持有对象
     */
    [obj retain];
    /**
     *  自己持有对象
     */

通过retain方法,非自己生成的对象,跟用alloc/new/copy/mutableCopy方法生成并持有的对象一样,成为了自己所持有的。

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

    /**
     自己生成并持有对象
     */
    id obj =[[NSObject alloc]init];
    [obj release];
    /**
     *  释放对象
     */

如此,用alloc方法由自己生成并持有的对象就通过release方法释放了,自己生成而非自己持有的对象,若用retain方法变为自己持有,也同样需要release方法释放

    /**
     取得非自己生成并持有对象
     */
    id obj =[NSMutableArray array];
    /**
     *  取得对象存在,但自己不持有对象
     */
    [obj retain];
    [obj release];

用alloc/new /copy/mutableCopy方法生成并持有的对象,或者用retain方法生成并持有的对象,一旦不在需要,务必需用release方法进行释放
另外我们会经常在开发中遇到autorelease一词,那么autorelease和release有什么区别的联系呢?
autorelease提供这样的功能,是对象在超出指定的生存范围时能够自动并正确的释放(调用release方法)
release是理解释放 autorelease并不是理解释放,而是注册到自动释放池autoreleasePool中,pool结束的时候再进行自动调用release释放
,例如通过NSMutableArray的array方法可以取得谁都不持有的对象,这些方法都是通过autorelease而实现的。
无法释放非自己持有的对象
对于用alloc/new/copy/mutableCopy方法生成并持有的对象,或使用retain方法持有的对象,由于持有者是自己,所以在不需要该对象时需要将其释放,而由此以外所得到的对象绝对不能释放。倘若应用程序中释放了非自己所持有的对象,就会造成程序崩溃
以上四项内容就是 ”引用计数式内存管理“ 的思考方式

1.4 苹果的实现####

因为NSObject类的源代码没有公开,此处利用Xcode的调试器(lldb)和iOS大概追溯其实现工程。在NSObject类的方法alloc上设置断点,醉追溯程序的执行。以下是程序执行所调用的方法和函数。
+ alloc
+ allocWithZone:
class_createInstance
calloc
alloc首先调用allocWithZone:类方法,然后调用class_createInstance函数,该函数在Objective-C运行时参考中也有说明,然后通过调用calloc来分配内存块。 class_createInstance函数的源代码可以通过objc4库中的runtime/objc-runtime-new.mm进行确认。
retainCount/release/retain的实现又是怎样的呢?和刚才的方法一样,在这里列出了各个方法调用的方法和函数:
– retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey

- retain
__CFDoExternRefOperation
CFBasicHashAddValue

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

各个方法都通过调用了一个 _ _CFDoExternRefOperation函数,调用了一系列名称相似的函数。如这些函数名的前缀“CF”,他们包含于 Core Foundation框架源代码中,即是CFRuntime.c _ _ _CFDoExternRefOperation函数。为了理解其实现,下面简化了__CFDoExternRefOperation后的源代码。

CF/CFRuntime.c __CFDoExternRefOperation

int __CFDoExternRefOperation(uintptr_t op, id obj){
    CFBasicHashRef table = 取得对象对应的散列表(obj);
    int count;
    switch (op) {
        case OPERATION_retaionCount:
            count = CFBasicHashGetCountOfKey(table,obj);
            return count;

        case OPERATION_retaion:
            count = CFBasicHashAddValue(table,obj);
            return obj;

        case OPERATION_release:
            count = CFBasicHashRemoveValue(table,obj);
            return 0 == count;
    }
}

__CFDoExternRefOperation函数按retainCount/retain/release操作进行分发,调用不同的函数。NSObject类的retainCount/retain/release实例方法也许如下面代码所示:

-(NSUInteger)retainCount{
    return (NSUInteger)__CFDoExternRefOperation(OPERATION_retaionCount,self);
}
-(id)retain{  
    return (id)__CFDoExternRefOperation(OPERATION_retaion,self);
}
-(void)release{
    return (NSUInteger)__CFDoExternRefOperation(OPERATION_release,self);
}

可以从__CFDoExternRefOperation函数以及由此函数调用的各个函数名看出,苹果的实现大概就是采用散列表(引用计数表)来管理引用计数。

1.5 autorelease####

autorelease就是自动释放,这看上去很像ARC,但实际上他更类似于C语言中的自动变量(局部变量的特征)。
在C语言中,若某自动变量超出其作用域,该自动变量将被自动遗弃。
autorelease会像C语言的自动变量那样来对待对象实例。当超出其作用域(相当于变量作用域)时,对象实例的release实例方法将会被调用。另外,同C语言不同的是,变成人员可以设置变量的作用域。

autorelease的具体使用方法如下,
(1)生成并持有NSAutoreleasePool对象
(2)调用已分配对象的autorelease实例方法
(3)废弃NSAutoreleasePool对象

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

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
    id obj = [[NSObject alloc]init];
    [obj autorelease];
    [pool drain];
    原文作者:刘高见
    原文地址: https://www.jianshu.com/p/885034812ebf
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞