iOS学习笔记(8)——ARC与所有权修饰符——strong,weak和autorelease

ARC与所有权修饰符——strong,weak和autorelease(内容来自我的《Objective-C 高级编程:iOS与OS X多线程和内存管理》读书笔记)


一、引用计数(Reference Counting)
首先,对于iOS中的引用计数:
1、在objective-C的对象中存在引用计数这一整数值。
2、调用alloc/retain方法后,对象的引用计数值+1。
3、调用release方法后,对象的引用计数值-1。
4、对象的引用计数值为0时,系统调用dealloc方法废弃该对象。


iOS所使用的引用计数式的内存管理方式,其思想可以概括为四点:
1、自己生成的对象,自己持有
使用以下名称开头的方法名:alloc、new、copy、mutableCopy。
如:id obj = [[NSObject alloc] init];
或者 id obj = [NSObject new];
copy方法:由各类实现copyWithZone:方法,生成并持有对象的副本(mutableCopy实现mutableCopyWithZone:方法)。
2、非自己生成的对象,自己也能持有
如:id obj = [NSMutableArray array];
[obj retain];
只有通过retain方法,非自己生成的对象才能成为自己所持有的对象。
如果某个方法在其方法体中生成对象,并返回该对象,那么该方法的调用者也能持有该对象,比如:
-(id)allocObject
			{
				id obj = [[NSObject alloc] init];
				return obj;
			}
			id obj1 = [obj0 allocObject];  // 取得非自己生成的对象并持有该对象

3、不再需要自己持有的对象时释放
①、使用release方法。
如:id obj = [[NSObject alloc] init];
[obj release];
对于使用retain方法持有的非自己生成的对象,也能用release释放。
②、使用类方法(如:[NSMutableArray array])可以取得谁都不持有的对象,这些方法都是通过autorelease实现。
如:
-(id)object
{
id obj = [[NSObject alloc] init];
[obj autorelease];

return obj;
}

id obj1 = [obj0 object];

此时,也能够通过retain方法将调用autorelease方法取得的对象变为自己持有:
id obj1 = [obj0 object];
[obj1 retain];
4、无法释放非自己持有的对象
对于那些自己持有的对象,不使用时需要将其释放,而如果在程序中释放了非自己持有的对象时,会造成应用崩溃。

二、autorelease

对于C语言中的局部变量而言,它只在其作用域内有效,超出作用域的局部变量不可被访问。autorelease类似地对待对象示例,对于超出作用域的对象,其release实例方法将被调用。使用方法如下:

1、生成并持有NSAutoReleasePool对象;

2、调用对象的autorelease方法,将其加入到AutoReleasePool中;

3、释放NSAutoReleasePool对象,对于所有调用过autorelease方法的实例对象,释放pool对象同时调用这些对象的release方法。

如下代码所示:

NSAutoReleasePool *pool = [NSAutoReleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; // 释放pool对象

autorelease的实现

-(id)autorelease
{
	[NSAutoReleasePool addObject:self];
}
从代码中可以看出,autorelease方法的本质是调用NSAutoReleasePool的addObject类方法。

那么,如果调用NSAutoReleasePool对象的autorelease方法会发生什么结果?

结果就是发生异常。通常在使用Foundation框架时,无论调用哪一个对象的autorelease方法,其实现上都是调用NSObject类的autorelease实例方法,但是对于NSAutoReleasePool类而言,其autorelease方法已被该类重载,因此运行就会返回异常。


三、ARC

ARC即Automatic Reference Counting,自动引用计数,其本质还是引用计数式内存管理,但是正如名称中的“Automatic”所描述的那样,ARC只是自动地帮我们处理“引用计数”。

开启ARC的方法:

  • 使用clang(LLVM)3.0或以上版本的编译器
  • 设置编译器属性为“-fobj-arc"
ARC有效时,id类型和对象类型必须附加所有权修饰符,一共四种:__strong, __weak, __unsafe__unretained, __autoreleasing。

__strong修饰符
该修饰符是所有id类型和对象类型的默认修饰符,没有为id和对象类型指定所有权修饰符时,其默认为__strong类型。下面两行代码是等价的。
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
__strong修饰符表示对对象的强引用,持有强引用的变量在超出其作用域时被自动废弃,而随着强引用的失效,变量所引用的对象也相应地被释放。
对于下述代码:
{
	// 自己生成并持有对象
	id __strong obj = [[NSObject alloc] init];
	
	// 由于obj为强引用,故自己持有对象
}
// 由于变量obj超出其作用域,强引用失效,
// 自动释放自己持有的对象,对象的所有者已不存在,废弃该对象
由上可知,__strong修饰符通过变量的作用域可以正确地管理对象的所有者及其生命周期。
同样,附有__strong修饰符的变量之间可以相互赋值,在赋值上也能正确地管理对象的所有者:
// obj0 持有对象A的强引用
id __strong obj0 = [[NSObject alloc] init];  // 对象A

// obj1 持有对象B的强引用
id __strong obj1 = [[NSObject alloc] init];  // 对象B

id __strong obj2 = nil; // obj2 不持有任何对象

obj0 = obj1;
// obj0持有对象B的强引用(由obj1赋值而来)
// obj0持有的对对象A的强引用失效,此时,对象A的所有者不存在,废弃对象A
// 此时,对象B的所有者为obj0和obj1

obj2 = obj0;

obj1 = nil;

obj0 = nil;

obj2 = nil;
// 此时,对象B的所有者不存在,废弃对象B
__strong修饰符也可以修饰类的成员变量以及方法参数:
@interface Test : NSObject
{
	id __strong obj_;
}

-(void)setObject:(id __strong)obj;
@end

@implementation Test
-(id)init
{
	self = [super init];
	return self;
}

-(void)setObject:(id __strong)obj
{
	obj_ = obj;
}

@end
总结:通过使用__strong修饰符,可以达成“自己生成的对象,自己持有”和“非自己生成的对象,自己也能持有”,从而省去了retain代码。而通过废弃__strong变量(由于超过变量作用域或者成员变量所属的对象被废弃)或对变量重新赋值,可以做到“不再需要自己持有的对象时释放”。由于无需再写release代码,故也满足“非自己持有的对象无法释放”。这些都满足引用计数式的内存管理方式。

__weak修饰符
讲引用式内存管理就不得不讲到它所面临的一个重大问题——循环引用。而这个问题是__strong修饰符所无法解决的。
iOS学习笔记(8)——ARC与所有权修饰符——strong,weak和autorelease_第1张图片
对于上面的Test类,有以下的循环引用状态:
{
	// test0持有对象A的强引用
	id test0 = [[Test alloc] init];
	
	// test1持有对象B的强引用
	id test1 = [[Test alloc] init];
	
	[test0 setObject:test1];
	// 这句代码使得对象A的_obj成员变量持有对象B的强引用
	// 此时,对象A的_obj变量和test1都持有对象B的强引用
	
	[test1 setObject:test0];
	// 对象B的_obj成员变量持有对象A的强引用
	// 而此时,对象B的_obj变量和test0都持有对象A的强引用
}
// 由于test0变量超出作用域,强引用失效,自动释放对象A

// 由于test1变量超出作用域,强引用失效,自动释放对象B

// 此时,持有对象A的强引用的变量为对象B的_obj变量
// 持有对象B的强引用的变量为对象A的_obj变量

// 此时,由于对象A和对象B的_obj成员变量都相互持有对方的强引用
// 对象A和对象B在超出其生命周期后都无法被释放,发生内存泄露
同样,一个对象持有其自身时,也会发生循环引用:
id test = [[Test alloc] init];
[test setObject:test];
如何解决循环引用的问题?
__weak修饰符与__strong修饰符相反,提供弱引用,弱引用不能持有对象的实例。
id __weak obj = [[NSObject alloc] init];
但是如果编译以上代码,编译器会发出警告,因为变量obj持有对象的弱引用,而弱引用不能持有对象的实例,这意味着生成的NSObject对象会立即被释放。
正确的代码形式如下:(先将对象赋值给__strong类型的变量,然后再赋值给__weak类型的变量)
{
	id __strong obj0 = [[NSObject alloc] init];
	id __weak obj1 = obj0; // obj1变量持有对象的弱引用
}
由于__weak修饰符的变量不持有对象,因此,当变量超出其作用域时,对象会立即被释放。对于以下代码,先前使用__strong修饰符的情况下可能发生循环引用,但是将类的成员变量改成__weak修饰符的类成员变量的话,即可避免循环引用问题。
@interface Test : NSObject
{
       id __weak obj_;
}
-(void)setObject : (id __strong) obj;
@end
__weak修饰符有另一优点,在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil状态(空弱引用)
id __weak obj1 = nil;

{
	id __strong obj0 = [[NSObject alloc] init];
	obj1 = obj0;
	NSLog(@"A: %@", obj1);
}
NSLog(@"B: %@", obj1);

执行结果:
A: 
B: (null)


__autoreleasing修饰符

当ARC有效时,不能使用autorelease方法,也不能使用NSAutoreleasePool类。
// ARC无效时
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

id obj = [[NSObject alloc] init];

[obj autorelease];

[pool drain];

// ARC有效时
@autoreleasepool{
	id __autoreleasing obj = [[NSObject alloc] init];
}

上述代码为使用“@autoreleasepool块”来代替“NSAutoreleasePool类对象的生成、持有以及废弃”这一范围。
而且,当ARC有效时,要通过将对象赋值给__autoreleasing修饰符的变量来代替调用autorelease方法(将对象赋值给__autoreleasing修饰的变量等价于当ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool)。也可以理解为:ARC有效时,用@autoreleasepool块代替NSAutoreleasePool类,用__autoreleasing修饰的变量代替autorelease方法。
但是,我们通常非显式地使用__autoreleasing修饰符。这是因为,编译器会检查方法名是否以alloc/new/copy/mutableCopy开头,如果不是,则自动将返回值的对象注册到autoreleasepool。
@autoreleasepool{
	// 取得非自己生成并持有的对象
	id __strong obj = [NSMutableArray array];
	
	// 变量obj为强引用,所以自己持有对象
	// 而且,该对象由编译器判断其方法名后(非alloc/new/copy/mutableCopy开头)
	// 自动注册到autoreleasepool
}
// 变量obj超出其作用域,强引用失效,自动释放自己持有的对象
// 同时,随着@autoreleasepool块的结束,注册到autoreleasepool中得所有对象被自动释放
// 因对象的所有者不存在,废弃对象
在访问__weak修饰的变量时,必定要访问注册到autoreleasepool的对象,这是因为:__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象可能被废弃,而如果把要访问的对象注册到autoreleasepool中,在@autoreleasepool块结束之前都能确保该对象存在。
以下两种代码等价:
id __weak obj1 = obj0;
NSLog (@"class=%@", [obj1 class]);


id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog (@"class=%@", [tmp class]);

ARC的使用规则
  • 不能使用retain/release/retainCount/autorelease
    设置ARC有效时,禁止再次键入retain/release代码。
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 必须遵守内存管理的方法命名规则
    在ARC无效时,用于生成/持有对象的方法必须遵守以下命名规则:alloc/new/copy/mutableCopy 以上述名称开始的方法在返回对象时,必须返回给调用方所应当持有的对象。
    在ARC有效时,上述规则不变,再追加一条:init方法。以init开头的方法必须是实例方法,且必须要返回对象。返回的对象应为id类型或该方法声明类的对象类型,抑或是该类的父类型或子类型。而且, 该返回对象并不注册到autoreleasepool上。
  • 不要显示调用dealloc
  • 使用@autoreleasepool块代替NSAutoreleasePool
  • 不能使用NSZone
  • 对象型变量不能作为C语言结构体(struct/union)的成员
  • 显示转换"id"和"void *"

你可能感兴趣的:(iOS学习笔记)