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
开始,如果不是则自动
将返回值的对象注册到autoreleasepool
。alloc/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
修饰符。
网友评论