美文网首页
自动引用计数(ARC)

自动引用计数(ARC)

作者: MichealXXX | 来源:发表于2018-10-16 11:47 被阅读0次

    ARC这个词汇对于我们iOS开发人员而言可谓是再熟悉不过了,但是它是如何实现的这个很多人都没有去深究过,其中也包括我,现在我们就试着一探究竟。

    autorelease

    在说ARC之前我们先来看看autorelease,顾名思义autorelease就是自动释放,看上去很像ARC,但实际上它更像C语言中自动变量的特性。autorelease会像C语言对待自动变量那样对待对象实例,当其超出作用域时,对象实例release实例方法被调用,与C不同,编程人员可以设定变量的作用域

    autorelease的具体使用方法:
    1.生成并调用NSAutoreleasePool对象;
    2调用已分配对象的autorelease实例方法;
    3.废弃NSAutoreleasePool对象。

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

    在cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成,持有和废弃处理,因此开发者不一定非得使用NSAutoreleasePool来进行开发工作。但在大量产生autorelease对象时,我们有必要在适当的地方生成,持有,废弃NSAutoreleasePool对象。

    autorelease实现

    GNUstep中,autorelease的源码

    -(id)autorelease{
        [NSAutoreleasePool addObject:self];
    }
    

    本质就是调用NSAutoreleasePool对象的addObject类方法。调用实例对象的autorelease方法,该对象将被追加到正在使用的NSAutoreleasePool对象的数组里。

    再看一下废弃的方法

    [pool drain];
    
    - (void)drain{
        [self dealloc];
    }
    
    - (void)delloc{
        [self emptyPool];
        [array release];
    }
    
    - (void)emptyPool{
        for (id obj in NSArray) {
            [obj release];
        }
    }
    

    数组中所有的对象都会调用release实例方法。

    苹果的实现autorelease的行为和GNUstep可以说完全一样,通过苹果的源码我们可以来对比一下

    class AutoreleasePoolPage{
        static inline void *push(){
            //相当于生成持有NSAutoreleasePool对象
        }
        
        static inline void *pop(void *token){
            //相当于废弃NSAutoreleasePool对象
            releaseAll()
        }
        
        static inline id autorelease(id obj){
            //相当于NSAutoreleasePool类的addObject方法
            AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的AutoreleasePoolPage实例
            autoreleasePoolPage->add(obj)
        }
        
        id *add(id obj){
            将对象追加到内部数组中
        }
        
        void releaseAll(){
            调用数组中对象的release方法
        }
    };
    
    void *objc_autoreleasePoolPush(void){
        return AutoreleasePoolPage::push();
    }
    
    void *objc_autoreleasePoolPop(void *ctxt){
        AutoreleasePoolPop::pop(ctxt);
    }
    
    void *objc_autorelease(id obj){
        return AutoreleasePoolPage::autorelease(obj);
    }
    

    实现方式完全相同。

    ARC规则

    “引用计数式的内存管理”的本质在ARC中并没有改变,ARC只是自动的帮我们处理了“引用计数”

    ARC有效时,id类型和对象类型上必须附加所有权修饰符所有权修饰符一共有四种:
    _strong
    _weak
    _unsafe_unretained
    _autoreleasing
    我们就来分别说明一下这几种。

    _strong

    _strong修饰符是id类型和对象类型默认的所有权修饰符。

    id obj = [[NSObject alloc] init];
    //ARC有效等同于
    id _strong obj = [[NSObject alloc] init];
    

    附有_strong修饰符的变量在超出作用域时,即在该变量被废弃时,会释放其被赋予的对象。_strong表示对对象的强引用,超出作用域强引用失效,引用的对象会被废弃。

    通过_strong修饰符,不必再次键入retain或者release,完美的实现了内存管理的思考方式,然而_strong并不能解决所有问题。

    ps:_strong为默认修饰符,所以声明变量时不需要写上。

    _weak

    看起来_strong修饰符是完美的,但遗憾的是仅通过_strong是无法解决有些重大问题的

    @interface Test : NSObject
    {
        id obj;
    }
    - (void)setObject:(id)obj;
    @end
    
    @implementation Test
    
    - (id)init{
        self = [super init];
        return self;
    }
    
    - (void)setObject:(id)obj{
        obj_ = obj;
    }
    
    @end
    
    {
        id test0 = [[Test alloc] init];
        //test0 持有Test对象A的强引用
        id test1 = [[Test alloc] init];
        //test1 持有Test对象B的强引用
        [test0 setObject:test1];
        //Test对象A的成员变量obj持有Test对象B的强引用
        
        //持有Test对象B的强引用变量为test1以及Test对象A的成员变量obj
        [Test1 setObject:test0];
        //Test对象B的成员变量obj持有Test对象A的强引用
        
        //持有Test对象A的强引用变量为test0以及Test对象B的成员变量obj
    }
    
    //超出作用域
    //test0超出作用域强引用失效 Test对象A被释放
    
    //test1超出作用域强引用失效 Test对象B被释放
    
    //此时Test对象A的强引用被对象B的成员变量obj所持有
    
    //此时Test对象B的强引用被对象A的成员变量obj所持有
    
    //内存泄漏
    

    循环引用容易发生内存泄漏,应当废弃的对象在超出作用域后继续存在,这时使用_weak就可避免循环引用。弱引用不能持有对象实例,所以在超出作用域时,对象即被释放。

    _unsafe_unretained

    _unsafe_unretained正如其名称所示,是不安全的所有权修饰符,尽管ARC式的内存管理是编译器的工作,但附有_unsafe_unretained的变量不属于编译器的内存管理对象。

    _unsafe_unretained的变量同_weak作用一样,防止循环引用,在变量超出作用域时既不持有强引用也不持有弱引用,对象便被释放,在iOS4以及OS X Snow Leopard的应用程序中还未引入_weak因此要使用_unsafe_unretained替代。

    _autoreleasing

    ARC环境下不能使用autoreleasing方法,也不能使用NSAutoreleasePool类,但实际上ARC有效autorelease是起作用的。要通过将对象赋值给附加了_autoreleasing修饰符的变量来替代调用autorelease方法,等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool

    但是显示的附加_autoreleasing和显示的附加_strong一样罕见,编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepoolalloc/new/copy/mutableCopy开始的方法在返回对象时,是必须返回给调用方所应当持有的对象,既然必须持有那么超出作用域自然会释放。

    ARC的实现

    ARC“由编译器进行内存管理的”,但实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行时库的协助。下面我们看一下具体实现的代码。

    _strong修饰符

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

    实际底层函数(便于理解做了转换)

    {
        id obj = objc_msgSend(NSObject,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        objc_release(obj);
    }
    

    可以看到虽然ARC有效时不能使用release方法,但是编译器自动插入了release,我们在看看alloc/new/copy/mutableCopy以外的方法会怎样

    id _strong obj = [NSMutableArray array];
    

    实际底层函数

    {
        id obj = objc_msgSend(NSMutableArray,@selector(array));
        objc_retainAutoreleaseReturnValue(obj)
        objc_release(obj);
    }
    

    前后都同之前一样但是中间插入了objc_retainAutoreleaseReturnValue函数,这个函数主要用于最优化程序运行,它用于自己持有对象的函数,但它持有的对象应为返回注册在autoreleasepool对象的方法,或是函数的返回值,在调用alloc/new/copy/mutableCopy以外的方法之后,由编译器插入该函数。

    这种函数一般是成对出现的,与之相对的是objc_autoreleaseReturnValue(obj),它用于alloc/new/copy/mutableCopy以外的类方法返回对象的实现。例如[NSMutableArray array]方法。

    + (id)array{
        return [[NSMutableArray alloc] init];
    }
    
    + (id)array{
        id obj = objc_msgSend(NSMutableArray,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        return objc_autoreleaseReturnValue(obj)
    }
    

    objc_autoreleaseReturnValue函数会检查使用该函数的方法或者函数调用方的执行命令列表,如果方法或函数的调用方调用了方法或函数后紧接着调用了objc_retainAutoreleaseReturnValue函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方

    _weak修饰符

    若附有_weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量,若使用_weak修饰符的变量,即是注册到autoreleasepool中的对象。我们来看看它的实现。

    {
        id _strong obj = [[NSObject alloc] init];
        id _weak obj1 = obj;
    }
    

    编译器的模拟代码

    {
        id obj1;
        objc_initWeak(&obj1,obj);
        objc_destroyWeak(&obj1);
    }
    

    通过objc_initWeak初始化附有_weak修饰符的变量,在变量作用域结束时通过objc_destroyWeak函数释放该变量。

    objc_initWeak函数将附有_weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数。objc_destroyWeak函数将0作为参数调用objc_storeWeak函数,即前面的源代码与下列源代码相同。

    {
        id obj1;
        obj1 = 0;
        objc_storeWeak(&obj1,obj);
        objc_storeWeak(&obj1,0);
    }
    

    objc_storeWeak函数把第二参数的赋值对象的地址作为健值,将第一参数的附有_weak修饰符的变量的地址注册到weak表中。如果第二参数为0,则把变量的地址weak表中删除。

    weak表与引用计数表相同,作为散列表被实现。如果使用weak表,将废弃对象的地址作为健值进行检索,就能高速获取对应附有_weak修饰符的变量的地址

    释放对象时,废弃谁都不持有的对象的同时程序的动作如下:
    1.objc_release
    2.因为引用计数为0所以执行dealloc
    3._objc_rootDealloc
    4.objc_dispose
    5.objc_destructInstance
    6.objc_clear_deallocating

    objc_clear_deallocating函数的动作如下:

    1.从weak表中获取废弃对象的地址作为健值的记录。
    2.将包含在记录中所有的附有_weak修饰符变量的地址,赋值为nil
    3.从weak表中删除该记录。
    4.从引用计数表中删除废弃对象的地址为健值的记录。

    由此可知大量使用附有_weak修饰符的变量,则会消耗相应的CPU资源。良策是只在需要避免循环引用时使用_weak修饰符。

    相关文章

      网友评论

          本文标题:自动引用计数(ARC)

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