美文网首页
自动引用计数(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