这篇博客记录ARC的学习过程
自动引用计数是指内存管理中对引用采取用自动计数的技术。
在Objective-C中采用ARC机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次输入retain或者release代码。这在降低程序崩溃,内存泄漏等风险的同时,减少了工作量。编译器完全清楚目标对象,并且能立刻释放那些不再被使用的对象。这样的话,程序会具有可预测性,能流畅运行,速度也会大大提升。
在LLVM编译器中设置ARC为有效状态,就无需再次输入retain或者是release代码
在上面这些情况下编译源代码时,编译器将自动进行内存管理
我们利用开灯关灯来举例说明引用计数
为了判断是否有人在办公室里,我们导入计数功能“需要照明的人数”。
第一个人进入办公室,“需要照明的人数”加1。计数值从0变到了1。此时开灯
每当有人进入办公室,“需要照明的人数”加1。如计数值由1变成了2
每当有人下班离开办公室,“需要照明的人数”减1。如计数值由2变成了1
最后一个人离开办公室时,“需要照明的人数”减1。计数值从1变为了0。因此需要关灯
这样就能在不需要照明的时候保持关灯状态。
对象相当于照明设备,一台计算机可以处理多个对象
“对象的使用环境”相当于上班进入办公室的人。虽然这里的“环境”有时候也指程序中运行的代码,变量,变量作用域,对象等,但概念上就是使用对象的环境。
对照明设备做的动作 | 对Objective-C对象做的动作 |
---|---|
开灯 | 生成对象 |
需要照明 | 持有对象 |
不需要照明 | 释放对象 |
关灯 | 废弃对象 |
对象操作 | Objective-C方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy 等方法 |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc方法 |
这些有关Objective-C的内存管理的方法,包含在Cocoa框架中用于OS X,iOS应用开发。Cocoa框架中Foundation框架库的NSObject类负担内存管理的职责。
使用以下名称开头的方法名意味着自己生成的对象只有自己持有
这里说到的“自己”对应前文的“对象的使用环境”,将它理解成程序员自身也是没错的。
id obj = [[NSObject alloc] init];
使用NSObject类的alloc类方法就能自己生成并持有对象。指向生成并持有对象的指针被赋给变量obj。另外,使用new类方法也能生成并持有对象。使用[NSObject new]和[[NSObject alloc] init]是完全一样的
id obj = [NSObject new];
copy方法利用基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。与copy方法类似,mutableCopy方法利用基于NSMutableCopying方法约定,由各类实现的muatbleCopyWithZone:方法生成并持有对象的副本。两者的区别在于:copy方法生成不可变的对象,而mutableCopy方法生成可变更的对象。这类似于NSArray类对象与NSMutableArray类对象的差异。用这些方法生成的对象,虽然是对象的副本,但同alloc,new方法一样,在“自己生成并持有对象”上没有改变
使用以下名称也意味着自己生成并持有对象
但是对于以下名称,即使alloc/new/copy/mutableCopy名称开头,并不属于同一类别的方法
这里是驼峰拼写法来命名
用上述之外方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。我们使用alloc/new/copy/mutableCopy以外的方法看看。
//取得非自己生成并持有的对象id obj = [NSMutableArray array];
//取得的对象存在,但是自己不持有[obj retain];
//自己持有对象
通过retain方法,非自己生成的对象跟用alloc/new/copy/mutableCopy方法生成并持有的对象一样,成为了自己持有的
自己持有的对象,一旦不再需要,持有者有义务释放其对象。使用使用release方法。
//自己生成并持有对象
id obj = [[NSObject alloc] init];//自己持有对象
[obj release];//释放对象
//指向对象的指针仍然保留在变量obj中,貌似能够访问。
//但是对象一经释放绝对不可访问
如此,用alloc方法由自己生成并持有的对象就通过release方法释放了。自己生成而非自己所持有的对象,若用retain方法变为自己持有,也同样可以用release方法释放
//取得非自己生成并持有的对象
id obj = [NSMutableArray array];
//取得的对象存在,但自己不持有对象
[obj retain];
//自己持有对象
[obj release];
//释放对象
//对象不可再被访问
用alloc/new/copy/mutableCopy方法生成并持有的对象,或者用retain方法持有的对象,一旦不再需要,务必要用release方法进行释放。
如果要用某个方法生成对象,并将其返回给该方法的调用方,那么他的源代码是怎样的?
-(id)allocObject {
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
return obj;
原封不动地返回alloc方法生成并持有的对象,就能让调用方也持有该对象。
//取得非自己生成并持有的对象
id obj1 = [obj0 allocObject];
//自己持有对象
allocObject名称符合前文的命名规则,因此它与用alloc方法生成并持有对象的情况完全相同,所以使用allocObject方法也就意味着“自己生成并持有对象”
那么,调用[NSMutableArray array]方法使取得的对象存在,但自己不持有该对象,又是如何实现的呢?不能使用以alloc/new/copy/mutableCopy开头的方法名,因此要使用object这个方法名
-(id)object {
id obj = [[NSObject alloc] init];
//自己持有对象
[obj autorelease];
//取得的对象存在,但自己不持有该对象
return obj;
}
autorelease方法可以使取得的对象存在,但自己不持有对象。autorelease提供这样的功能,使对象在超出指定的生存范围时能够自动并正确释放(调用release方法)
使用NSMutableArray类的array类方法等可以取得谁都不持有的对象。这些方法都是通过autorelease而实现的。这些用来取得谁都不持有的对象的方法名不能以alloc/new/copy/mutableCopy开头。
id obj1 = [obj0 object];
//取得的对象存在,但自己不持有对象
也能通过retain方法将autorelease方法取得的对象变为自己持有
id obj1 = [obj0 object];
//取得的对象存在,但自己不持有对象
[obj1 retain];
//自己持有对象
对于用alloc/new/copy/mutableCopy方法生成并持有的对象,或者是用retain方法持有的对象,由于持有者是自己,所以不需要对象时将其释放。而由此以外得到的对象绝不能释放。如果在应用程序中释放了非自己所持有的对象就会造成崩溃。
//自己生成并持有对象
id obj = [[NSobject alloc] init];
//自己持有对象
[obj release];
//对象已释放
[obj release];
//释放之后再次释放非自己持有的对象
//应用程序崩溃
//崩溃情况:再度风气已经废弃了的对象时崩溃
//访问已经废弃的对象时崩溃
或者是“取得的对象存在,但自己不持有对象”时释放
id obj1 = [obj0 object];
//取得的对象存在,但自己不持有该对象
[obj1 release];
//释放了非自己持有的对象
//导致程序崩溃
以上例子所示,释放非自己持有的对象时会导致程序崩溃,因此不要去释放非自己持有的对象
id obj = [NSObject alloc];
上述调用了NSObject类的alloc类方法在NSObejct.m源代码中的实现如下:
通过allocWithZone:类方法调用NSAllocateObject函数分配了对象
NSAllocateObject函数通过调用NSZoneMalloc函数来分配存放对象的内存空间,之后将该内存空间置为0,最后返回作为对象而使用的指针
NSDefaultMallcoZone,NSZoneMalloc等名称中的NSZone是为防止内存碎片化而引入的结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的,对象的大小分配内存,从而提高了内存管理的效率。
但是,如同苹果官方文档中所说,现在的运行时系统只是简单的忽略了区域的概念,运行时系统中的内存管理本身已经极具效率,使用区域来管理内存反而会引起内存效率低下以及源码复杂化等问题
以下是去掉NSZone后简化了的源代码
alloc类方法用struct obj_layout中的retained证书来保存引用计数,并将其写入对象内存头部,该对象内存快全部置0后返回。以下用图来展示有关GNUstep的实现,alloc类方法返回的对象如图所示
id obj = [[NSObject alloc] init];
NSLog(@"retainCount = %d", [obj retainCount]);
//retainCount = 1
由对象寻址到对象内存头部,从而访问其中的retained变量。
因为分配时全部置0,所以retained为0,由NSExtraRefCount(self)+1得出,retainCount为1,可以推测出,retain方法使retained变量为1,而release方法使retained变量减1。
[obj retain];
写入了retained变量超出最大值发生异常的代码,但实际上只运行了使retained变量加1的retained++代码。同样的,release实例方法进行retained–。并在该引用计数变量为0的时候处理。
[obj release];
以下为此release实例方法的实现
同预想的一样,当retained变量大于0时减1,等于0时调用dealloc方法,废弃对象。
仅废弃dealloc分配的内存块
在NSObject类的alloc类方法上设置断点,追踪程序的执行
alloc类方法首先调用allocWithZone:类方法,和GNUstep的实现相同,然后调用class_createInstance函数,该函数在Objective-C运行时参考中也有说明,最后通过调用calloc来分配内存块。这和前面讲述的GNUstep的实现并没有多大差异。class_createInstance函数的源代码可以通过Objc4库中的runtime/objc-runtime-new.mm进行确认
retainCount/retain/release实例方法怎样实现呢?
各个方法都通过同一个调用了_CFDoExternRefOperation函数,调用了一系列名称相似的函数。如这些函数名的前缀“CF”所示,他们包含于Core Foundation框架源代码中,即是CFRuntime.c的__CFDoExternRefOperation函数,为了理解其实现,下面简化了__CFDoexternRefOperation函数后的源代码
__CFDoExternRefOperation函数按retainCount/retain/release操作进行分发,调用不同的函数。NSObject类的retainCount/retain/release实例方法也如下所示
苹果的实现大概就是采用散列表(引用计数表)来管理引用计数
GNUstep将引用计数保存在对象占用内存块头部变量中,而苹果的实现,则是保存在引用计数表的记录中。GNUstep的实现看起来既简单又高效,而苹果如此实现必然有其好处。
通过内存块头部管理引用计数的好处如下:
autorelease就是自动释放。这好像比较ARC,但实际上它更类似C语言中的自动变量的特性
C语言的自动变量:程序执行时,若某自动变量超出其作用域,则该自动变量将被废弃
autorelease会像C语言的自动变量那样来对待对象实例。当超出其作用域(相当于变量作用域),对象实例的release实例方法被调用。另外,同C语言的自动变量不一样的是,程序员可以自己设定变量的作用域
autorelease的使用方法:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];id obj = [[NSObject alloc] init];[obj autorelease];[pool drain]//[pool drain]等同于[obj release];
在Cocoa框架中,相当于主程序的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象来进行生成,持有和废弃处理。因此,应用程序开发者不一定非得使用NSAutoreleasePool对象来进行开发工作。
尽管如此,但大量产生uotorelease的对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象。典型的例子是读入大量图片改变其尺寸。图像文件读入到NSData对象,并从中生成UIImage对象,改变对象尺寸后生成新的UIImage对象。这个时候就会产生大量autorelease的对象
在适当的地方生成,持有或废弃NSAutoreleasePool对象
Cocoa框架中也有很多类方法用于返回aitorelease对象。比如NSMutableArray类的arrayWithCapacity类方法。
id array = [NSMutableArray arrayWithCapacity:1];//等同于id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
#import int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...id obj = [[NSObject alloc] init];[obj autorelease];}return 0;
}
autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法
提高调用Objective-C方法的速度
GNUstep中的autorelease实际上是一种特殊的方法来实现的。这种方法能够高效地运行OS X,iOS用应用程序中频繁调用autorelease方法,他被称为“IMP Caching”。在进行分方法调用时,为了解决类名/方法名以及取得方法运行时的函数指针,要在框架初始化时对其结果进行缓存。
这就是IMP Caching的方法调用,虽然同以下源代码完全相同,但从运行效率上看,即使它依赖于运行环境,一般而言速度也是其他方法的两倍。
addObject类方法调用正在使用的NSAutoreleasePool对象的addObject实例方法,被赋予pool变量的即为正在使用的NSAutoreleasePool对象
如果嵌套生成或持有的NSAutoreleasePool对象,理所当然会使用最内侧的对象。pool2为正在使用的NSAutoreleasePool对象
实际上的GNUstep实现使用的是连接列表,这同在NSMutableArray对象中追加对象参数是一样的。如果调用NSObject类的autorelease实例方法,该对象将被追加到正在使用的NSAutoreleasePool对象中的数组里。
[pool drain];
以下为通过drain实例方法废弃正在使用的NSAutoreleasePool对象的过程
虽然调用了好几个方法,但是可以确定的是对于数组中所有的对象都调用了release方法
C++类中虽然有动态数组的实现,但其行为和GNUstep的实现完全相同。
我们使用调试器来观察一下NSAutoreleasePool类方法和autorelease方法的运行比较过程。这些方法调用了关联于obj4库autorelease实现的函数
可通过NSAutoreleasePool类中的调试用非公开类方法showPoolds来确认已被autorelease对对象的状况。showPools会将现在的NSAutoreleasePool的状况输出到控制台
[NSAutoreleasePool showPools];
NSAutoreleasePool类的showPools类方法只能在iOS中使用,作为替代,在现在的运行时系统中我们使用调试非公开函数_objc_autoreleasePoolPrint()
该函数在检查对象是否被自动release时非常有效
autorelease NSAutoreleasePool对象
如果autorelease NSAutoreleasePool对象会如何?
NSAutoreleasePool* pool= [[NSAutoreleasePool alloc] init]; [pool autorelease];
发生异常
通常在使用Objective-C,也就是Foundation框架时,无论调用哪一个对象的autorelease实例方法,实现上是调用的NSObject类的autorelease实例方法。但是对于NSAutoreleasePool类,autorelease实例方法已经被该类重载,因此运行时会出错。
引用计数式内存管理的本质在ARC中并没有改变,就像“自动引用计数”这个名称表示的那样,ARC只是自动地帮助我们处理“引用计数”的相关部分
在编译单位上,可设置ARC有效或无效,这一点便能佐证上述结论。比如对每个文件可选择使用或不使用ARC
这些东西在ARC有效时也是可行的。只是在源代码的记述方法上有所不同。
Objective-C编程中为了处理对象,可讲变量类型定义为is类型或者各种对象类型
所谓对象类型就是指向NSObject这样的Objective-C类的指针,例如“NSObject*”。id类型用于隐藏对象类型的类名部分,相当于C语言中常用的“void”
ARC有效时,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符。
__strong修饰符是id类型和对象类型默认的所有权修饰符。也就是说,下面代码中的id变量,实际上被附加了所有权修饰符
id和对象类型在没有明确指定所有权修饰符时,默认为__strong修饰符。
id obj = [[NSObject alloc] init];
//两行代码效果相同
id __strong obj = [[NSObject alloc] init];
//ARC无效时
id obj = [[NSObject alloc] init];
{
id __strong obj = [[NSObject alloc] init];
}
此代码明确指定了C语言变量的作用域。
//ARC无效时
{
id obj = [[NSObject alloc] init];
[obj release];
为了释放生成并持有的对象,增加了调用release方法的代码。该源代码进行的动作同先前ARC有效时的动作完全一样
如此源代码所示,附有__strong修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。
如“strong”这个名称所示,__strong修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
//代码中关于对象的所有者的部分
id __strong obj = [[NSobject alloc] init];
此源代码就是之前自己生成并持有对象的源代码。
{//自己生成并持有对象id __strong obj = [[NSObject alloc] init];//因为变量obj为强引用//所以自己持有对象
}//变量obj超出其作用域,强引用失效//自动的释放自己持有的对象//对象的所有者不存在,因此废弃对象
对象的所有者和生存周期是明确的,那么,在取得非自己生成并持有的对象时又会如何呢?
{id __strong obj = [[NSMutableArray alloc] init];
}
在NSMutableArray类的array类方法的源代码中取得非自己生成并持有的对象。具体如下
{id __strong obj = [NSMutableArray array];//因为变量obj为强引用,所以自己持有对象
}//因为变量obj超出其作用域,强引用失效//所以自动的释放自己持有的对象
在这里对象的所有者和对象的生存周期也是明确的
{//自己生成并持有对象id __strong obj = [[NSObject alloc] init];//因为变量obj强引用,所以自己持有对象}//因为变量obj超出其作用域,强引用失效,所以自动的释放自己持有的对象//对象的所有者不存在,因此废弃对象
附有__strong修饰符的变量之间可以相互赋值
id __strong obj0 = [[NSObject alloc] init];id __strong obj1 = [[NSObject alloc] init];id __strong obj2 = nil;obj0 = obj1;obj2 = obj0;obj1 = nil;obj0 = nil;obj2 = nil;
看一下生成并持有对象的强引用
id __strong obj0 = [[NSObject alloc] init];//对象A//obj0持有对象A的强引用id __strong obj1 = [[NSObject alloc] init];//对象B//obj1持有对象B的强引用id __strong obj2 = nil;//obj2不持有任何对象obj0 = obj1;//obj0持有obj1的赋值对象B的强引用//因为obj0被赋值,所以原先持有的对对象A的强引用失效//对象A的所有者不存在,因此废弃对象A//此时,持有对象B的强引用的变量为//obj0和obj1obj2 = obj0;//obj2持有由obj0赋值的对象B的强引用//此时,持有对象B的强引用的变量为obj0,obj1,和obj2obj1 = nil;//因为nil被赋予了obj1,所以对对象B的强引用失效//此时,持有对象B的强引用的变量为//obj0和obj2obj0 = nil;//因为nil被赋予obj0,所以对对象B的强引用失效//此时,持有对象B的强引用的变量为obj2obj2 = nil;//因为nil被赋予obj2,所以对对象B的强引用失效//对象B的所有者不存在,因此废弃对象B
__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确地管理其对象的所有者。
即便是Objective-C类成员变量,也可以在防范措施上,使用附有__strong修饰符的变量。
#import NS_ASSUME_NONNULL_BEGIN@interface Test : NSObject {id __strong obj_;
}
- (void)setObject :(id __strong)obj;
@endNS_ASSUME_NONNULL_END#import "Test.h"@implementation Test
- (id)init {self = [super init];return self;
}
- (void)setObject:(id __strong)obj {obj_ = obj;NSLog(@"11111");
}
@endint main(int argc, const char * argv[]) {id __strong test = [[Test alloc] init];[test setObject:[[NSObject alloc] init]];}
{id __strong test = [[Test alloc] init];//test持有Test对象的强引用[test setObject:[[NSObject alloc] init]];//Test对象的obj_成员//持有NSObject对象的强引用}//因为test变量超出其作用域,强引用失败//所以自动释放Test对象//Test对象的所有者不存在,因此废弃该对象//废弃Test对象的同时,Test对象的obj_成员也被废弃//NSObject对象的强引用失效//自动释放NSObject对象//NSObject对象的所有者不存在,因此废弃该对象
无需额外工作便可以使用类成员以及方法参数中。
__strong修饰符同后面要讲的__weak修饰符和__autoreleasing修饰符一起,可以保证将附有这些修饰符的自动变量初始化为nil。
id __strong obj0;id __weak obj1;id __autoreleasing obj2;
如苹果宣称的那样,通过__strong修饰符,不必在此键入retain或者release,完美的满足了“引用计数式内存管理思考方式”。
__strong修饰符修饰的成员变量在持有对象时,很容易发生循环引用。
#import NS_ASSUME_NONNULL_BEGIN@interface Test : NSObject {id __strong obj_;
}
- (void)setObject :(id __strong)obj;
@endNS_ASSUME_NONNULL_END#import "Test.h"@implementation Test
- (id)init {self = [super init];return self;
}
- (void)setObject:(id __strong)obj {obj_ = obj;NSLog(@"11111");
}
@end#import
#import "Test.h"int main(int argc, const char * argv[]) {@autoreleasepool {id test0 = [[Test alloc] init];//对象A//tets0持有Test对象A的强引用id test1 = [[Test alloc] init];//对象B//test1持有Test对象B的强引用[test0 setObject:test1];//Test对象A的obj_成员变量持有Test对象B的强引用//此时持有Test对象B的强引用变量为Test对象A的obj_和test1[test1 setObject:test0];//Test对象B的obj_成员变量持有Test对象A的强引用//此时,持有Test对象A的强引用的变量为//Test对象B的obj_和test0}//test0变量超出其作用域,强引用失效,自动释放Test对象A//test1变量超出其作用域,强引用失效,自动释放Test对象B//此时,持有Test对象A的强引用的变量为Test对象B的obj_//此时,持有Test对象B的强引用的变量为Test对象A的obj_//发生内存泄漏}
这样的代码就会发生循环引用。
循环引用很容易发生内存泄漏。所谓内存泄漏就是指应当废弃的对象在超出其生存周期后继续存在。
此代码的本意是赋予变量test0的对象A和赋予变量test1的对象B在超出其变量作用域时被释放,即在对象不被任何变量持有的状态下予以废弃。但是循环引用使得对象不能被废弃
//虽然只有一个变量,但在该对象持有其自身时,也会发生循环引用id test = [[Test alloc] init];[test setObject:test];
如何避免循环引用?
使用__weak修饰符就可以避免循环引用
__weak修饰符和__strong修饰符相反,提供弱引用。弱引用不能持有对象实例。
id __weak obj = [[NSObject alloc] init];
如果直接这样去加上__weak修饰符,会发生警告。提示将保留对象分配给弱变量,因为自己不持有对象,对象将在分配后释放
id __strong obj0 = [[NSObject alloc] init];id __weak obj1 = obj0;
这样将对象复制给附有__strong修饰符的变量之后在赋值给附有__weak修饰符的变量,就不会发生警告了
//自己生成并持有对象id __strong obj0 = [[NSObject alloc] init];//因为obj0变量为强引用//所以自己持有对象id __weak obj1 = obj0;//obj1变量持有生成对象的弱引用}
因为带__weak修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。如果我们先将可能发生循环引用的类成员变量改成附有__weak修饰符的成员变量的话,就可以避免这个现象。
#import NS_ASSUME_NONNULL_BEGIN@interface Test : NSObject {id __weak obj_;
}
- (void)setObject :(id __strong)obj;
@endNS_ASSUME_NONNULL_END
__weak修饰符,如果该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)
id __weak obj1 = nil;{id __strong obj0 = [[NSObject alloc] init];obj1 = obj0;NSLog(@"A:%@", obj1);}NSLog(@"B:%@", obj1);}
id __weak obj1 = nil;{//自己生成并持有对象id __strong obj0 = [[NSObject alloc] init];//因为obj0变量为强引用//所以自己持有对象obj1 = obj0;//obj1变量持有对象的弱引用NSLog(@"A:%@", obj1);//输入obj1变量持有的弱引用的对象}//因为obj0变量超出其作用域,强引用失效//所以自动释放自己持有的对象//因为对象无持有者,所以废弃该对象//废弃对象的同时,持有该对象的弱引用的obj1变量的弱引用失效,nil被赋值给obj1NSLog(@"B:%@", obj1);//输出赋值给obj1变量中的nil}
像这样使用__weak修饰符可以避免循环引用,通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值 的对象是否已经废弃
顾名思义,__unsafe_unretained修饰符是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。
id __unsafe_unretained obj = [[NSObject alloc] init];
这段代码将自己生成并持有的对象复制给附有__unsafe_unretained修饰符的变量中。
附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。到这里,__unsafe_unretained修饰符和__weak修饰符是一样的。
id __unsafe_unretained obj1 = nil;{id __strong obj0 = [[NSObject alloc] init];obj1 = obj0;NSLog(@"A:%@", obj1);}NSLog(@"B:%@", obj1);
id __unsafe_unretained obj1 = nil;{//自己生成并持有对象id __strong obj0 = [[NSObject alloc] init];//因为obj0变量为强引用//所以自己持有对象obj1 = obj0;//虽然obj0变量赋值给obj1//但是obj1变量记不持有对象的强引用也不持有弱引用NSLog(@"A:%@", obj1);//输出obj1变量表示的对象}//因为obj0变量超出其作用域,强引用失效,//所以自动释放自己持有的对象//因为对象无持有,所以废弃该对象NSLog(@"B:%@", obj1);//输出obj1变量表示的对象//obj1变量表示的对象已被废弃(垂悬指针)//错误访问
最后一行NSLog只是碰巧正常运行而已,虽然访问了已经被废弃的对象,但是应用程序在个别运行状况下才会崩溃
在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被赋值的对象确实存在。
在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_unretained修饰符来代替__weak修饰符。赋值给附有__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用程序会崩溃
ARC有效时autorelease会如何呢?不能使用autorelease方法,另外,也不能使用NSAutoreleasePool类,这样一来,虽然autorelease无法直接使用,但实际上,ARC有效时autorelease功能是起作用的。
//ARC无效NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];id obj = [[NSObject alloc] init];[obj autorelease];[pool drain];
ARC有效时,可以写成这样
@autoreleasepool {id __autoreleasing obj = [[NSObject alloc ] init];}
指定@auytoreleasepool块开代替“NSAutoreleasePool类对象生成,持有以及废弃”这一范围。
另外ARC有效时,要通过将对象赋值给附加了__autoreleasing修饰符的变量来代替调用autorelease方法。对象赋值给附有__autoreleasing修饰符的变量等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool。
在ARC有效时,用@autoreleasepool块代替NSAutoreleasePool类,用附有__auto releasing修饰符的变量代替autorelease方法
但是,显式的附加__autoreleasing修饰符同显式地附加__strong修饰符一样罕见
取得非自己生成并持有的对象时,如同下面的源代码,虽然可以使用alloc/new/copy/mutableCopy以外的方法来取得对象,但是对象已经被注册到了autoreleasepool。这同在ARC无效时调用了autorelease方法的对象是一样的。这是由于编译器会见车方法名是否是以alloc/new/copy/mutableCopy开始,如果不是则将自动返回值的对象注册到autoreleasepool
init方法返回值的对象不注册到autoreleasepool
@autoreleasepool {//取得非自己生成并持有的对象id __strong obj = [[NSMutableArray alloc ] init];//因为变量obj为强引用//所以自己持有对象//并且该对象由编译器判断其方法名后,自动注册到autoreleasepool}//因为变量obj超出其作用域,强引用失效//所以自动释放自己持有的对象//同时随着@autoreleasepool块的结束//注册到autoreleasepool中的所有对象都被自动释放//因为对象的所有者不存在,所以废弃对象
像这样,不使用__autoreleasing修饰符也能使对象注册到autoreleasepool。
+ (id) array {return [[NSMutableArray alloc] init];
}
//该源代码没有使用__autoreleasing修饰符,可以写成下面这样
+ (id) array {id obj = [[NSMutableArray alloc] init];return obj;
}
没有显示的指定所有权修饰符,所以id obj 同__strong修饰符的 id __strong obj是完全一样的。由于return使得对象变量超出其作用域,所以强引用对应的自己持有的对象会被自动释放。但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool
虽然__weak是为了避免循环引用出现的,但是在访问__weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象
id __strong obj0 = [[NSMutableArray alloc] init];id __weak obj1 = obj0;NSLog(@"class = %@", [obj1 class]);//和以下代码相同id __weak obj1 = obj0;id __autoreleasing tmp = obj1;NSLog(@"class = %@", [tmp class]);
__weak修饰符只持有对象的弱引用,而在访问一样东西的过程中,该对象有可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。因此,在使用附有__weak修饰符的变量时就必须使用注册到autoreleasepool中的对象。因此,在使用__weak修饰符的变量时就必定要使用注册到autoreleasepool中的对象
最后一个可非显示地使用__autoreleasing修饰符的例子,同前面讲述的id obj 和id __strong obj完全一样,由id __strong obj的例子类推出id __strong* obj吗?其实,退出来的是id __sutoreleasing* obj。同样的,对象的指针NSObject** obj便成为了NSObject* __autoreleasing* obj;
像这样,id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。
比如为了得到详细的错误信息,经常会在方法的参数中传递NSError对象的指针,而不是函数返回值。
__strong修饰符 /__weak修饰符
附有__strong修饰符,__weak修饰符的变量类似于C++中的智能指针std::shared_ptr和std::weak_ptr。std::shared_ptr可通过引用计数来持有C++类实例,std::weak_ptr可避免循环引用。在不得不使用没有__strong修饰符/__weak修饰符的C++时,强烈推荐使用这两种智能指针
在ARC有效的情况下编译源代码,必须遵守一些规则。
内存管理是编译器的工作,因此没有必要使用内存管理的方法(retain/release/retainCount/autorelease)
设置ARC有效时,无需再次输入retain或release代码
实际上,在ARC有效时,如果编译器使用了这些方法的源代码,就会出现下列错误。一旦使用便会出现编译错误,因此可以更准确的main数为“设置ARC有效时,禁止再次键入retain或者是release代码”
retainCount和release都会引起编译错误,因此不能使用以下代码
for (;;) {NSUInteger count = [obj retainCount];[obj release];if (count == 1) {break;}}
ARC无效时,该源代码也完全不符合引用计数式内存管理的思考方式,也就是说他在任何情况下都无法使用,所以没有问题
总之,只能在ARC无效且手动进行内存管理时使用retain/release/retainCount/autorelease方法
一般通过调用NSObject类的alloc类方法来生成并持有Objective-C对象
id obj = [NSObject alloc];
在ARC有效时,禁止使用NSAllocateObject函数,同retain等方法一样,如果使用便会引起编译错误
同样的,也禁止使用用于释放对象的NSDeallocateObject函数
ARC无效时,用于对象生成/持有的方法必须遵守以下的命名规则
alloc
new
copy
mutableCopy
以上述名称的方法在返回对象时,必须返回调用方所应当持有的对象。这在ARC有效时也一样,返回的对象没有完全改变。只是在ARC有效时要追加一条命名规则
init
以init开始的方法的规则要比alloc/new/copy/mutableCopy更为严格。该方法必须是实例方法,并且必须返回对象。返回的对象为id类型或该方法声明类的对象类型,抑或是该类的超类型或子类型。该返回对象并不注册到autoreleasepool上,基本上只是对alloc方法返回值的对象进行初始化处理并返回该对象
id obj = [[NSObject alloc] init];
如此源代码所示,init方法会初始化alloc方法返回的对象,然后原封不动地返回给调用方。
- (id) initWithObject:(id)obj;
该方法声明遵守了命名规则,但像下面这个方法虽然也以init开始,却没有返回对象,因此不能使用
- (void) initThisObject;
另外,下面的虽然也是以init开始的方法但并不包含在上述的命名规则里
- (void) initialize;
无论ARC是否有效,只要对象的所有者都不持有该对象,该对象就被废弃。对象被废弃时,不管ARC是否有效,都会调用对象的dealloc方法
- (void)dealloc {}
如果使用C语言库,在该库内部分配缓存时,dealloc方法需要通过free来释放流出的内存
dealloc方法在大多数情况下还适用于删除已注册的代理或者观察者对象
- (void)dealloc {[[NSNotificationCenter defaultCenter] removeObserver:self];
}
在ARC无效时,要像下面这样调用[super dealloc]
//ARC无效
- (void)dealloc {[super dealloc];
}
ARC有效时会遵循无法显式调用dealloc这一规则,如果使用就会同release等方法一样,引起编译错误
ARC会自动对此进行处理,因此不必书写[super dealloc]。dealloc中只需记述废弃对象时所必须的处理
ARC有效时,使用@autoreleasepool块代替NSAutoreleasePool
NSAutoreleasePool类不可使用时便会引起编译错误
虽说ARC有效时,不能使用区域(NSZone)。不管ARC是否有效,区域在现在的运行时系统中已经单纯地被忽略
C语言的结构体成员中,如果存在Objective-C对象型变量,便会引起编译错误
C语言的规约上没有方法来管理结构体成员的生存周期。因为ARC把内存管理的工作分配给编译器,所以编译器必须能够知道并管理对象的生存周期。
要把对象类型变量加入到结构体成员中,可强制转换为void*或是附加__unsafe_unretained修饰符
附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。如果管理时不注意赋值对象的所有者,便有可能遭受内存泄漏或程序崩溃。
在ARC无效时,将id变量强制转换为void*变量并不会出问题
//ARC无效时id obj = [[NSObject alloc] init];void* p = obj;
在进一步,将void*变量赋值给id变量中,调用其实例方法,运行时也不会有问题
//ARC无效时id obj = [[NSObject alloc] init];void* p = obj;id o = p;[o release];
但是ARC有效时会引起编译错误
id型或对象型变量赋值给void*或你想赋值时都需要进行特定的转换。如果想要单纯的赋值,可以使用__bridge转换
id obj = [[NSObject alloc] init];void* p = (__bridge void*)obj;id o = (__bridge id)p;
像这样,通过__bridge转换,id和void就能够相互转换
但是转换为void的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符相近,甚至更低。如果管理时不注意复制对象的所有者,就会因为垂悬指针而导致程序崩溃。
__bridge转化中还有另外两种转换。
id obj = [[NSObject alloc] init];void* p = (__bridge_retained void*)obj;
__bridge_retained转换可使要转换赋值的变量也持有所赋值的对象。
//ARC无效id obj = [[NSObject alloc] init];void* p = obj;[(id)p return];
__bridge_retained转换为了retain。变量obj和变量p同时持有对象
变量作用域结束时,虽然随着持有强引用的变量obj失效,但由于__bridge_retained转换使变量p看上去处于持有该对象的状态,因此该对象不会被废弃。
//ARC无效void* p = 0;{id obj = [[NSObject alloc] init];//[obj retainCount]->1p = [obj retain];//[obj retainCount]->2[obj release];//[obj retainCount]->1}//[(id)p retainCount]-> 1//[obj retainCount]->1//对象仍然存在
__bridge_retained转换与retain类似,__bridge_transfer转换和release相似。在给id obj赋值时retain即相当于__strong修饰符的变量
如果使用这两种转换,那么不使用id类型或对象类型变量也可以生成,持有以及废弃对象
void* p = (__bridge_retained void*)[[NSObject alloc] init];NSLog(@"%@", [(__bridge id)p class]);(void)(__bridge_transfer id)p;
上面的代码和ARC无效时下面代码相同
//ARC无效id p = [[NSObject alloc] init];NSLog(@"%@", [p class]);[p release];
这些转换多使用在Objective-C对象和Core Foundation对象之间的相互变换
Objective-C对象与Core Foundation对象
Core Foundation对象主要在用C语言编写的Core Foundation框架中,并使用引用计数的对象。在ARC无效时,Core Foundation框架中的retain/release分别是CFRetain/CFRelease
Core Foundation对象和Objective-C对象的区别很小,不同之处只在于是由哪一个框架所生成的。无论是哪个框架生成的对象,一旦生成之后,便能在不同的框架中使用,Foundation框架的API生成并持有的对象可以用Core Foundation的框架的API释放
在ARC无效时,只用简单的C语言的转换就能实现Core Foundation对象和Objective-C对象的互换。
Foundation框架的API生成并持有的Objective-C对象能够作为Core Foundation对象来使用。也可以通过CFRelease来释放。当然,也可以使用__bridge_retained转换来替代CFBridgingRetain。
CFMutableArrayRef cfObject = (__bridge_retained CFMutableArrayRef)obj;
CFMutableArrayRef cfObject = NULL;{id obj = [[NSMutableArray alloc] init];//变量obj持有对生成并持有对象的强引用cfObject = CFBridgingRetain(obj);//通过CFBridgingRetain//将对象CFRetain赋值给变量cfObjectCFShow(cfObject);printf("retain count = %d\n", CFGetRetainCount(cfObject));//通过变量obj的强引用和CFBridgingRetain,引用计数为2}//变量铂金超出其作用域,强引用失效//引用计数为1printf("retain count after the scope = %d\n", CFGetRetainCount(cfObject));CFRelease(cfObject);//因为将对象CFRelease,引用计数为0,该对象被废弃
CFMutableArrayRef cfObject = NULL;{id obj = [[NSMutableArray alloc] init];//变量obj持有对生成并持有对象的强引用cfObject = (__bridge CFMutableArrayRef)obj;CFShow(cfObject);printf("retain count = %d\n", CFGetRetainCount(cfObject));//因为__bridge转换不改变对象的持有状况,所以只有通过变量obj的强引用,引用计数为1}//变量obj超出其作用域,强引用失效,对象得到释放,无持有者的对象被废弃//此后对象的访问出错printf("retain count after the scope = %d\n", CFGetRetainCount(cfObject));CFRelease(cfObject);
由此可知,__bridage_retained转换或CFBridgingRetain的转换是不可或缺的
与之前相反的由Core Foundation框架的API生成并持有的Core Foundation对象也能作为Objective-C对象来使用。
也可以使用__bridge_transfer转换代替CFBridgingRelease
必须恰当使用CFBridgingRetain/CFBridgingRelease或者__bridge_retained/__bridge_transfer转换。在将Objective-C变量赋值给C语言变量,即没有附加所有权修饰的void*等指针变量时,伴随一定的风险
在ARC有效时,Objective-C类的属性也会发生变化
@property (nonatomic, strong) NSSTring* name;
在ARC有效时,以下可以作为这种属性声明中使用的属性来用
以上各种属性赋值给指定的属性中就相当于赋值给附加各属性对应的所有权修饰符的变量中。只有copy属性不是简单的赋值,它赋值的是通过NSCopying接口的copyWithZone:方法复制赋值源所生成的对象。
在声明类成员变量时,如果同属性声明中的属性不一致会引起编译错误。
__unsafe_unretained修饰符以外的__strong/__weak/__autoreleasing修饰符保证其指定的变量初始化为nil。同样的,附有__strong/__weak/__autoreleasing修饰符变量的数组也保证其初始化为nil
数组超出其变量作用域时,数组中各个附有__strong修饰符的变量也随之失效,其强引用消失,所赋值的对象也随之释放。这和不使用数组的情形完全一样
将附有__strong修饰符的变量作为动态数组来使用时呢?这种时候,我们根据不同的目的选择NSMutableArray,NSMutableDictionary,NSMutableSet等Foundation框架的容器,这些容器会恰当的持有追加的对象并为我们管理这些对象。
在C语言的动态数组中也可以使用附有__strong修饰符的变量,要遵守如下事项
在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理。
使用memset等函数将内存填充为0也不会释放赋值的对象。者非常危险,只会引起内存泄漏。对于编译器,必须明确地使用赋值给附有__strong修饰符变量的源代码。所以要注意,必须将nil赋值给所有数组元素。
另外,使用memcpy函数拷贝数组元素以及realloc函数重新分配内存块也会有危险。数组元素所赋值的对象有可能被保留在内存中或是重复被废弃,所以这两个函数也禁止使用。
我们也可以像使用__strong修饰符那样使用附有__weak修饰符变量的动态数组。在__autoreleasing修饰符的情况下,因为与谁选哪个的使用方法有差异,所以最好不要使用动态数组。由于__unsafe_unretained修饰符在编译器的内存管理对象之外,所以它和void*类型一样,只能作为C语言的指针类型来使用
苹果的官方声明中说:ARC是“由编译器进行内存管理”的。但实际上只有编译器是无法完全胜任的。在此基础上还要有Objective-C运行时库的帮助。
两次调用objc_msgSend方法(alloc方法和init方法),变量作用域结束时通过objc_release释放对象,虽然ARC有效时不能使用release方法,但是由此可知编译器自动插入了release。下面我们看看使用alloc/new/copy/mutableCopy以外的方法会是什么情况。
id __strong obj = [NSMutableArray array];
objc_retainAutoreleasedReturnValue(obj)函数主要用于最优化程序运行。它是用于自己持有(retain)对象的函数,但它持有的对象应该为返回注册在autoreleasepool中对象的方法,或是函数的返回值。像该源代码这样,在调用alloc/new/copy/mutableCopy以外的方法,即NSMutableArray类的array类方法等方法后调用,由编译器插入该函数。
这种objc_retainAutoreleasedReturnValue函数是成对的,与之相对应的函数是objc_autoreleaseReturnValue。它用于alloc/new/copy/mutableCopy以外的NSMutableArray类方法等返回对象的实现上。
objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方法的执行命令列表,如果方法或函数的调用方在调用了方法或函数之后紧跟着直接调用objc_retainAutoreleasedReturnValue函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue函数和objc_retain函数不同,它即使不注册到autoreleasepool中而返回对象,也能够正确的获取对象。通过objc_autoreleaseReturnValue函数和objc_retainAutoreleasedReturnValue函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到最优化
id __weak obj1 = obj;
假设变量obj附加__strong修饰符且对象被赋值
通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束时通过objc_destoryWeak函数释该变量
objc_initWeak函数将附有__weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_destoryWeak函数将0作为参数调用objc_storeWeak函数
objc_storeWeak(&obj1, 0);
objc_storeWeak函数把第二参数的赋值对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中。如果第二参数为0,则把变量的地址从weak表中删除
weak表与引用计数表相同,作为散列表被实现。如果使用weak表,将被废弃的对象的地址作为键值进行检索,就能告诉地获取对应的附有__weak修饰符的变量地址。另外,由于一个对象可同时赋值给多个附有__weak修饰符的变量值中,所以对于一个键值,可注册多个变量的地址。
对象将通过objc_release函数释放
对象被废弃时最好调用的objc_clear_deallocating函数的动作如下
id __weak obj = [[NSObject alloc] init];
因为该源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量中,所以自己不能持有该对象,这时会被释放并被废弃,因此会引起编译器警告。
自己生成并持有的对象通过objc_initWeak函数被赋值给附有__weak修饰符的变量中,但是编译器判断其没有持有者,故该对象立即通过objc_release函数被释放和废弃。
这样的话,nil就会被赋值给引用废弃对象的附有__weak修饰符的变量。
立即释放对象
id __weak obj = [[NSObject alloc] init];
我们发现上面的代码引起了警告,这是由于编译器判断生成并持有的对象不能继续持有。附有__unsafe_unretained修饰符又会如何呢?
这是由于__weak修饰符完全相同,编译器判断生成并持有的对象不能继续持有,从而发出警告。
objc_release函数立即释放了生成并持有的对象,这样该对象的垂悬指针被赋值给变量obj中。
由于源代码不使用返回值的对象,所以编译器会发出警告
可以通过void型转换来避免发生警告
(void)[[NSObject alloc] init];
不管是否转换为void,该代码都会转换为
虽然没有指定赋值变量,但与赋值给附有__unsafe_unretained修饰符变量的源代码完全相同。由于不能继续持有生成并持有的对象,所以编译器生成了立即调用objc_release函数的源代码。由于ARC的处理,这样的源代码也不会造成内存泄漏。
在调用了生成并持有对象的实例方法后,该对象被释放。看来“由编译器进行内存管理”这句话应该是正确的
使用__weak修饰符的变量,即是使用注册到autoreleasepool中的对象
与被赋值时相比,在使用附有__weak修饰符的变量的情形下,增加了对objc_loadWeakRetained函数和objc_autorelease函数的调用。
因为附有__weak修饰符变量所引起的对象像这样被注册到autoreleasepool中,所以在@autoreleasepool块结束之前都应该可以放心使用。但是,如果大量使用附有__weak修饰符的变量,注册到autoreleasepool的对象也会大量增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后在使用。
将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法
不使用__autoreleasinga修饰符,仅使用__weak声明的变量也能将引用对象注册到autoreleasepool中。
上一篇:Android studio2022安装包下载及安装教程
下一篇:【Git】