1.1 自动引用计数(ARC,Automatic Reference Counting)
自动引用计数是指内存管理中对引用采取自动计数的技术。
“ 在LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者是release代码。”
1.2 内存管理/引用计数
1.2.1 计数的内存管理
<1> 对象操作与Objective-C方法的对应
<2> 对象操作所对应的Objective-C的方法和引用计数的变化如下:
<3> 有关Objective-C内存管理的方法并不包含在Objective-C语言中,而是在包含在Cocoa框架中。如下:
1.2.2 内存管理的思考方式
内存管理的思考方式有以下四种:
自己生成的对象,自己所持有
非自己生成的对象,自己也能持有
不再需要自己持有的对象时释放
非自己持有的对象无法释放
自己生成的对象,自己所持有
使用以下名称开头的方法意味着自己生成的对象只有自己持有:
- alloc
- new
- copy
- mutableCopy
🌰:
// alloc方法
id obj = [[NSObject alloc] init];//持有新生成的对象
//指向生成并持有[[NSObject alloc] init]的指针被赋给了obj,也就是说obj这个指针强引用[[NSObject alloc] init]这个对象。
//new方法
id obj = [NSObject new];//持有新生成的对象
注意1:
这种将持有对象的指针赋给指针变量的情况不只局限于上面这四种方法名称,还包括以他们开头的所有方法名称:
- allocThisObject
- newThatObject
- copyThisObject
- mutableCopyThatObject
注意2:
下列几个方法,并不属于同一类别的方法:
- allocate
- newer
- copying
- mutableCopyed
非自己生成的对象,自己也能持有
用alloc/new/copy/mutableCoy以外的方法取得对象,因为非自己生成并持有,所以自己不是该对象的持有者。
但是通过retain方法,非自己生成的对象跟用alloc/new/copy/mutableCoy方法生成并持有的对象一样,成为了自己所持有的
🌰:
id obj = [NSMutableArray array];//非自己生成并持有的对象
[obj retain];//持有新生成的对象
//注意: 这里[NSMutableArray array]返回的非自己持有的对象正是通过autorelease方法实现的。所以如果想持有这个对象,需要执行retain方法才可以
不再需要自己持有的对象时释放
- 自己持有的对象,一旦不再需要,持有者有义务释放该对象,务必使用release方法释放。
注意,是有义务,而不是有权利,注意两个词的不同
🌰:
id obj = [[NSObject alloc] init];//持有新生成的对象
[obj doSomething];//使用该对象做一些事情
[obj release];//事情做完了,释放该对象
id obj = [NSMutableArray array];//非自己生成并持有的对象
[obj retain];//持有新生成的对象
[obj soSomething];//使用该对象做一些事情
[obj release];//事情做完了,释放该对象
使用autorelease方法,可以使取得的对象存在,但自己不持有对象
用来取得谁都不持有的对象的方法名不能以alloc/new/copy/mutableCopy开头
通过retain方法也能将调用autorelease方法取得的对象变为自己持有
注意: autorelease提供了这样一个功能:在对象超出其指定的生存范围时能够自动并正确地释放
非自己持有的对象无法释放
在释放对象的时候,我们只能释放已经持有的对象,非自己持有的对象是不能被自己释放的。
两种不允许的情况:
1. 释放一个已经废弃了的对象
id obj = [[NSObject alloc] init];//持有新生成的对象
[obj doSomething];//使用该对象
[obj release];//释放该对象,不再持有了
[obj release];//释放已经废弃了的对象,崩溃
2. 释放自己不持有的对象
id obj = [NSMutableArray array];//非自己生成并持有的对象
[obj release];//释放了非自己持有的对象
思考:哪些情况会使对象失去拥有者呢?
- 将指向某对象的指针变量指向另一个对象。
- 将指向某对象的指针变量设置为nil。
- 当程序释放对象的某个拥有者时。
- 从collection类中删除对象时。
1.2.3 alloc/retain/release/dealloc实现
借助开源软件GNUstep的源代码中alloc/retain/release/dealloc的实现来理解苹果的Cocoa实现。总结如下:
- 在Objective-C的对象中存在引用计数这一整数值
- 调用alloc或是retain方法后,引用计数值加1
- 调用release方法后,引用计数值减1
- 引用计数值为0时,调用dealloc方法废弃对象
苹果的实现:
由于NSObject类的源代码没有公开,利用Xcode的调试器(lldb)和iOS大概追溯内存管理和引用计数的实现。通过追溯可以发现似乎和散列表(Hash)有关,这说明苹果对引用计数的管理应该是通过散列表来执行的。
在这张表里,key为内存块地址,而对应的值为引用计数。也就是说,它保存了这样的信息:一些被引用的内存块各自对应的引用计数。
那么使用散列表来管理内存有什么好处呢?
因为计数表保存内存块地址,我们就可以通过这张表来:
-
确认损坏内存块的位置。
- 在检测内存泄漏时,可以查看各对象的持有者是否存在。
1.2.4 autorelease
autorelease 介绍
当对象超出其作用域时,对象实例的release方法就会被调用,autorelease的具体使用方法如下:
- 生成并持有NSAutoreleasePool对象。
- 调用已分配对象的autorelease方法。
废弃NSAutoreleasePool对象。
所有调用过autorelease方法的对象,在废弃NSAutoreleasePool对象时,都将调用release方法(引用计数-1):
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];//相当于obj调用release方法
NSRunLoop在每次循环过程中,NSAutoreleasePool对象都会被生成或废弃。
如果有大量的autorelease变量,在NSAutoreleasePool对象废弃之前(一旦监听到RunLoop即将进入睡眠等待状态,就释放NSAutoreleasePool),都不会被销毁,容易导致内存激增的问题:
for (int i = 0; i < imageArray.count; i++)
{
UIImage *image = imageArray[i];
[image doSomething];
}
在这类情况下,有必要在适当的地方生成、持有或废弃NSAutoreleasePool对象。
for (int i = 0; i < imageArray.count; i++)
{
//临时pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = imageArray[i];
[image doSomething];
[pool drain];
}
思考:什么时候会创建自动释放池?
答:运行循环检测到事件并启动后,就会创建自动释放池,而且子线程的 runloop 默认是不工作的,无法主动创建,必须手动创建。
🌰:
自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则在出了作用域以后,自动释放对象会因为没有自动释放池去处理自己而造成内存泄露。
autorelease实现
同样借助开源软件GNUstep的源代码中autorelease的实现来理解苹果的autorelease实现。总结如下:
- autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法
- addObject类方法调用正在使用的NSAutoreleasePool对象的addObject实例方法
如果嵌套生成或持有的NSAutoreleasePool对象,理所当然会使用最内侧的对象
- 如果调用NSObject类的autorelease实例方法,该对象将被追加到正在使用的NSAutoreleasePool对象中的数组中
- drain实例方法在废弃autorelease对象数组之前,会先对数组中的所有对象调用release实例方法
苹果的实现:
autoreleasepool以一个队列数组的形式实现,主要通过下列三个函数完成.
• objc_autoreleasepoolPush(压入)
• objc_autoreleasepoolPop(弹出)
• objc_autorelease(释放内部)
1.3 ARC规则
1.3.1 内存管理的思考方式
ARC和非ARC机制下的内存管理思想是一致的:
- 自己生成的对象,自己持有。
- 非自己生成的对象,自己也能持有。
- 不再需要自己持有的对象时释放对象。
- 非自己持有的对象无法释放。
在ARC机制下,编译器就可以自动进行内存管理,减少了开发的工作量。
1.3.2 所有权修饰符
虽然在ARC机制下,编译器就可以自动进行内存管理。但我们有时仍需要四种所有权修饰符来配合ARC来进行内存管理。如下:
- __strong修饰符
- __weak修饰符
- __unsafe_unretained修饰符
- __autoreleasing修饰符
对象类型: 指向NSObject这样的Objective-C类的指针,如“NSObject ”。
id类型: 用于隐藏对象类型的类名部分,相当于C语言中的“void ”。
__strong修饰符
- __strong修饰符表示对对象的”强引用“。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
在__strong修饰符修饰的变量之间相互赋值的情况:
id __strong obj0 = [[NSObject alloc] init];//obj0 持有对象A
id __strong obj1 = [[NSObject alloc] init];//obj1 持有对象B
id __strong obj2 = nil;//ojb2不持有任何对象
obj0 = obj1;//obj0强引用对象B;而对象A不再被ojb0引用,被废弃
obj2 = obj0;//obj2强引用对象B(现在obj0,ojb1,obj2都强引用对象B)
obj1 = nil;//obj1不再强引用对象B
obj0 = nil;//obj0不再强引用对象B
obj2 = nil;//obj2不再强引用对象B,不再有任何强引用引用对象B,对象B被废弃
- 附有__strong修饰符的变量之间可以相互赋值。通过相互赋值,可以使得变量对对象的强引用失效,从而释放原先持有的对象,转而持有由另外一个变量赋值的新的对象的强引用。
- 即时是Objective-C类成员变量,也可以在方法参数上,使用附有__strong修饰符的变量
- __strong修饰符可以确保将附有__strong修饰符的自动变量(局部变量)初始化为nil(该规则适用于__weak修饰符和__autoreleasing修饰符)
<1> 通过__strong修饰符使ARC有效遵循了Objective-C内存管理的思考方式
- “自己生成的对象,自己持有”和“非自己生成的对象,自己也能持有”只需通过对带__strong修饰符的变量赋值便可达成
- 通过废弃带__strong修饰符的变量(变量作用域结束或是成员变量所属对象废弃)或者对变量赋值,都可以做到”不再需要自己持有的对象时释放“
- 由于不必再次键入release,所以”非自己持有的对象无法释放“原本就不会执行 。 --- (在ARC有效时不能使用release方法的缘故是编译器会自动插入release)
<2> __strong修饰符 是id类型和对象类型默认的所有权修饰符:
id obj = [NSObject alloc] init];
等同于
id __strong obj = [NSObject alloc] init];
其内存管理的过程:
{
id __strong obj = [NSObject alloc] init];//obj持有对象
}
//obj超出其作用域,强引用失效
__strong修饰符表示对对象的强引用。持有强引用的变量在超出其作用域时被废弃。
<3> __strong内部实现:
生成并持有对象:
{
id __strong obj = [NSObject alloc] init];//obj持有对象
}
编译器的模拟代码:
id obj = objc_mesgSend(NSObject, @selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);//超出作用域,释放对象
使用命名规则以外的构造方法 ,如NSMutableArray类的array类方法:
{
id __strong obj = [NSMutableArray array];
}
编译器的模拟代码:
/* 编译器的模拟代码 */
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue的作用:持有对象,将对象注册到autoreleasepool并返回。
其中,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:返回注册到autoreleasepool的对象。
<4>__strong内部实现的总结
如上所示, objc_retainAutoreleasedReturnValue函数和objc_autoreleaseReturnValue函数的协作,可以不将对象注册到autoreleasePool中而直接传递,这一过程达到了最优化。
- objc_autoreleaseReturnValue函数用于alloc/new/copy/mutableCopy方法以外的NSMutableArray类的array类方法等返回对象的实现上。
- objc_autoreleaseReturnValue函数与objc_autorelease函数不同,一般不仅限于注册对象到autoreleasePool中。
- objc_retainAutoreleasedReturnValue函数主要用于最优化程序运行。即,它是用于自己持有(retain)对象的函数,但它持有的对象应为返回注册在autoreleasePool中对象的方法或是函数的返回值。
- objc_retainAutoreleasedReturnValue函数与objc_retain函数不同,它即时不注册到autoreleasePool而返回对象,也能够正确地获取对象。
- objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧跟着调用objc_retainAutoreleasedReturnValue函数,那么就不将返回的对象注册到autoreleasePool中,而是直接传递到方法或函数的调用方。
__weak修饰符
<1>__weak修饰符提供弱引用。弱引用不能持有对象。
- 在持有“对象”的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0; // obj1变量持有NSObject对象的弱引用
NSLog(@“A : %@”, obj1);
// output A : <NSObject: 0x731e180>
}
NSLog(@“B : %@”, obj1; // output B : (null)
- 通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃
<2> __weak修饰符大多解决的是循环引用的问题。
🌰:
@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
其引用的关系如下:
{
id test0 = [[Test alloc] init];//test0强引用对象A
id test1 = [[Test alloc] init];//test1强引用对象B
[test0 setObject:test1];//test0强引用对象B
[test1 setObject:test0];//test1强引用对象A
}
因为生成对象(第一,第二行)和set方法(第三,第四行)都是强引用,所以会造成两个对象互相强引用对方的情况:
所以,我们需要打破其中一种强引用:
@interface Test:NSObject
{
id __weak obj_;//由__strong变成了__weak
}
- (void)setObject:(id __strong)obj;
@end
这样一来,二者就只是弱引用对方了:
<3> __weak内部实现
{
id __weak obj1 = obj;
}
编译器的模拟代码:
id obj1;
objc_initWeak(&obj1,obj);//初始化附有__weak的变量
id tmp = objc_loadWeakRetained(&obj1);//取出附有__weak修饰符变量所引用的对象并retain
objc_autorelease(tmp);//将对象注册到autoreleasepool中
objc_destroyWeak(&obj1);//释放附有__weak的变量
使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象。
其中,objc_initWeak函数初始化附有__weak的变量。将附有__weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数,将obj对象以&obj1作为key放入一个weak表(Hash)中。
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_destroyWeak函数释放附有__weak的变量。将0作为参数调用objc_storeWeak函数,在weak表中查询&obj1这个键,将这个键从weak表中删除。
objc_storeWeak(%obj1, 0);
<4> __weak内部实现的总结
通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束时通过objc_destroyWeak函数释放该变量。
objc_destroyWeak函数把第二参数的赋值对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中。如果第二参数为0,则把变量的地址从weak表中删除。
因为同一个对象可以赋值给多个附有__weak的变量中,所以对于同一个键值,可以注册多个变量的地址。
当一个对象不再被任何人持有,则需要释放它,其过程为:
- objc_dealloc
- dealloc
- _objc_rootDealloc
- objc_dispose
- objc_destructInstance
- objc_clear_deallocating
- 从weak表中获取废弃对象的地址
- 将包含在记录中的所有附有__weak修饰符变量的地址赋值为nil
- 从weak表中删除该记录
- 从引用计数表中删除废弃对象的地址
- 因为附有__weak修饰符变量所引起的对象像这样被注册到autoreleasepool中,所以在@autoreleasepool块结束之前都可以放心使用。
- 如果大量地使用附有__weak修饰符的变量,注册到autoreleasepool的对象也会大量地增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后再使用。
__autoreleasingd修饰符
<1>__autoreleasing使用方法
-
ARC下,可以用@autoreleasepool来替代NSAutoreleasePool类对象,用__autoreleasing修饰符修饰变量来替代ARC无效时调用对象的autorelease方法(对象被注册到autoreleasepool)。
- 不需要显式地附加__autoreleasing修饰符,因为编译器会检查方法名是否已alloc/new/copy/mutableCopy方法开始,如果不是则自动将返回值的对象注册到autoreleasePool。
@autoreleasePool{
id __strong obj = [NSMutableArray array];
/*
obj变量持有对象的强引用
并且该对象由编译器判断其方法名后,
自动注册到autoreleasePool
*/
}
/ * obj变量超出其作用域,强引用失效
所以自动释放自己持有的对象
同时,随着@autoreleasePool块的结束,
注册到autoreleasePool中的所有对象被自动释放
因为对象的所有者不存在,所以废弃该对象
*/
- 访问附有__weak修饰符的变量时,实际上必定要访问注册到autoreleasePool的对象。
id __weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);//实际访问的是注册到自动个释放池的对象
注意一下两段等效的代码里,NSLog语句里面访问的对象是不一样的,它说明:
在访问\_\_weak修饰符的变量(obj1)时必须访问注册到autoreleasepool的对象(tmp)。
为什么呢?
因为\_\_weak修饰符只持有对象的弱引用,也就是说在将来访问这个对象的时候,无法保证它是否还没有被废弃。
因此,如果把这个对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。
-
id的指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符,如下:
为了得到详细的错误信息,经常在方法的参数中传递NSError对象的指针,而不是函数返回值。
// 方法声明
- (Bool)performOperationWithError:(NSError **)error;
// 等同于
- (Bool)performOperationWithError:(NSError *__autoreleasing *)error;
// 应用
NSError *error = nil;
Bool result = [obj performOperationWithError:&error];
// 上述源代码经过编译器的转化:
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
Bool result = [obj performOperationWithError:&tmp];
error = tmp;
/*
编译器正是通过这种添加源代码的方式使得原先的源代码即不会编译出错,也能在使用 参数取得对象时,贯彻内存管理的思考方式。
*/
- 在显式地指定__autoreleasing修饰符时,必须注意对象变量要为自动变量(包括局部变量、函数以及方法参数)
<2> __autoreleasing内部实现
将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法。
alloc/new/copy/mutableCopy方法群:
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
编译器的模拟代码:
id pool = objc_autoreleasePoolPush();//pool入栈
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);// 将对象添加到autoreleasepool中
objc_autoreleasePoolPop(pool);//pool出栈
NSMutableArray类的array类方法:
@autoreleasepool{
id __autoreleasing obj = [NSMutableArray array];
}
编译器的模拟代码:
id pool = objc_autoreleasePoolPush();//pool入栈
id obj = objc_msgSend(NSMutableArray, @selctor(array));
objc_retainAutoreleasedReturnValue(obj);//用于自己持有(retain)对象
objc_autorelease(obj); // 将对象添加到autoreleasepool中
objc_autoreleasePoolPop(pool);//pool出栈
在这里我们可以看到pool入栈,执行autorelease,出栈的三个方法。
1.3.3 ARC的规则
我们知道了在ARC机制下编译器会帮助我们管理内存,但是在编译期,我们还是要遵守一些规则:
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必须遵守内存管理的方法名规则
- 不要显式调用dealloc
- 使用@autorelease块代替NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结构体的成员
- 显式转换id和void*
1. 不能使用retain/release/retainCount/autorelease
在ARC机制下使用retain/release/retainCount/autorelease方法,会导致编译器报错。
2. 不能使用NSAllocateObject/NSDeallocateObject
在ARC机制下使用NSAllocateObject/NSDeallocateObject方法,会导致编译器报错
3. 必须遵守内存管理的方法名规则
对象的生成/持有的方法必须遵循以下命名规则:
- alloc
- new
- copy
- mutableCopy
- init
对于init方法的要求则更为严格:
- 必须是实例方法
- 必须返回对象
- 返回对象的类型必须是id类型或方法声明类的对象类型
4. 不要显式调用dealloc
对象被废弃时,无论ARC是否有效,系统都会调用对象的dealloc方法。
我们只能在dealloc方法里写一些对象被废弃时需要进行的操作(例如移除已经注册的观察者对象)但是不能手动调用dealloc方法。
注意在ARC无效的时候,还需要调用[super dealloc]:
- (void)dealloc
{
//该对象的处理
[super dealloc];
}
5. 使用@autorelease块代替NSAutoreleasePool
ARC下须使用使用@autorelease块代替NSAutoreleasePool。
6. 不能使用区域(NSZone)
NSZone已经在目前的运行时系统(OBC2被设定的环境)被忽略了。
7. 对象型变量不能作为C语言结构体(struct/union)的成员
C语言的结构体如果存在Objective-C对象型变量,便会引起错误,因为C语言在规约上没有方法来管理结构体成员的生存周期 。
要把对象型变量加入到结构体成员中时,可强制转换为void * 或是附加__unsafe_unretained修饰符
8. 显式转换id和void*
非ARC下,这两个类型是可以直接赋值的
id obj = [NSObject alloc] init];
void *p = obj;
id o = p;
但是在ARC下就会引起编译错误。为了避免错误,我们需要通过__bridege来转换(单纯地赋值)。
id obj = [[NSObject alloc] init];
void *p = (__bridge void*)obj;//显式转换
id o = (__bridge id)p;//显式转换
__bridge_retained转换可使要转换赋值的变量也持有所赋值的对象。与retain类似。
void *p = 0;
{
id obj = [[NSObject alloc] init]; // 对象的retaidedCount : 1
p = (__bridge_retained void *)obj; // 对象的retainedCount : 2
}
// 作用域结束时,obj变量持有对象的强引用失效,所以释放持有对象,但是由于__bridge_retained转换使变量p看上去处于持有该对象的状态,因此该对象不会被废弃
NSLog(@"class = %@", [(__bridge id)p class]);
__bridge_transfer,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。与release类似。
1.3.5 属性
当ARC有效时,属性声明的属性与所有权修饰符的对应关系,如下表:
其中unsafe_unretained: unsafe_unretained表示存取方法会直接为实例变量赋值。
这里的“unsafe”是相对于weak而言的。我们知道weak指向的对象被销毁时,指针会自动设置为nil。而__unsafe_unretained却不会,而是成为空指针。需要注意的是:当处理非对象属性的时候就不会出现空指针的问题。
后记
这是《Objective-C高级编程》的第一部分,讲述了Objective-C的内存管理机制。通过对引用计数的增减来管理内存。ARC和非ARC机制下的内存管理思想是一致的。在ARC机制下,编译器就可以自动进行内存管理,减少了开发的工作量。
但我们有时仍需要四种所有权修饰符来配合ARC来进行内存管理。
__strong修饰符是id类型和对象类型默认的所有权修饰符。
__weak修饰符大多解决的是由__strong修饰符造成的循环引用的问题。
__autoreleasing修饰符修饰变量来替代ARC无效时调用对象的autorelease方法(对象被注册到autoreleasepool)。不需要显式地附加__autoreleasing修饰符。只有在id的指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符,例如:为了得到详细的错误信息,经常在方法的参数中传递NSError对象的指针
__unsafe_unretained指向的对象被销毁时不会自动设置为nil而是成为空指针。当处理非对象属性的时候就不会出现空指针的问题。