Objective-C内存管理(一)

作者: 见哥哥长高了 | 来源:发表于2016-07-30 12:15 被阅读139次

    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];
    

    相关文章

      网友评论

        本文标题:Objective-C内存管理(一)

        本文链接:https://www.haomeiwen.com/subject/eiznjttx.html