正文
基本概念
ARC为自动引用计数,引用计数式内存管理的本质并没有改变,ARC只是自动处理“引用计数”的相关部分。
在编译上,可以设置ARC有效或无效。默认工程中ARC有效,若设置无效,则指定编译器属性为
-fno-objc-arc
。
内存管理思考方式
同引用计数相同
-
自己生成的对象,自己持有
-
非自己生成的对象,自己也能持有
-
自己持有的对象不需要时释放
-
非自己持有的对象无法释放
所有权修饰符
-
__strong
-
__weak
-
__unsafe_unretained
-
__autoreleasing
__stong修饰符
__strong修饰符是id类型和对象类型默认的所有权修饰符。
也就是说,以下代码中的id变量实际上都被家了所有权修饰符
id __strong obj = [[NSObject alloc] init];
在ARC无效时,写法虽然同ARC有效时一致,但
{
id __strong obj = [[NSObject alloc] init];
}
以上代码明确指定了变量的作用域。
ARC无效时,该源码可记述如下:
/** ARC无效 */
{
id obj =[[NSObject alloc] init];
[obj release];
}
为了释放生成并持有的对象,增加调用了release方法的代码。该源代码进行的动作跟ARC有效时的动作完全一样。
如上所示,附有__strong修饰符的变量obj在超出其变量作用域是,即在该变量被废弃是,会释放其被赋予的对象。
__strong修饰符表示对对象的“强引用”。持有强你用的变量在超出其作用域时被废弃,虽则会强引用的失效,引用的对象会随之释放。
/**自己生成并持有对象*/
{
id __strong obj = [[NSObject alloc] init];
/**因为变量obj为强引用,所以自己持有对象*/
}
/*
*因为变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象。
*对象的所有者不存在,因为废弃该对象。
*/
取得非自己生成并持有的对象时,代码如下:
/**取得非自己生成并持有的对象*/
{
id __strong obj = [NSMutableArray array];
/**因为变量obj为强引用,所以自己持有对象*/
}
/**因为变量obj超出其作用范围,强引用失效,所以自动地释放自己持有的对象*/
变量的赋值如下:
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和obj1
*/
obj2 = obj0;
/*
*obj2持有由obj0赋值的对象B的强引用
*此时,持有对象B的强引用的变量为obj0,obj1和obj2;
*/
obj1 = nil;
/*
*因为nil被赋予了obj1,所以对对象B的强引用失效。
*此时,持有对象B的强引用的变量为obj0和obj2;
*/
obj0 = nil;
/*
*因为nil被赋予了obj1,所以对对象B的强引用失效。
*此时,持有对象B的强引用的变量为obj0和obj2;
*/
obj2 = nil;
/*
*因为nil被赋予了obj2,所以对对象B的强引用失效。
*因为对象B的所有者不存在,所以废弃对象B.
*/
__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确得管理其对象的所有者。同样,在方法的参数上,也可以使用附有__strong修饰符的变量。
@interface Test : NSObjecgt
{
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 __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。
通过__strong修饰符,不必再次retain或者release,就可以满足引用计数内存管理的思考方式。
-
自己生成的对象自己持有
-
非自己生成的对象,自己也可以持有
-
不再需要自己持有的对象时释放
-
非自己持有的对象无法释放
__weak修饰符
仅仅通过__strong修饰符不能够解决引用计数内存管理中的“循环引用”问题。
例如:
@interface Test : NSObjecgt
{
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];/* 对象A */
/*
* test0 持有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_
* 发生内存泄漏
*/
循环引用容易发生内存泄漏。内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。
以下代码也会引起内存泄漏(对自身的强引用)
id test = [[Test alloc] init];
[test setObject:test];
使用__weak修饰符可以避免循环引用。
__weak修饰符与__strong修饰符相反,提供弱引用。弱引用不能持有对象实例。
id __weak obj = [[NSObject alloc] init];
如果运行以上代码,编译器会发出警告。
Assigning retained object to weak variable; object will be released after assignment
以上代码将自己生成并持有的对象赋值给附有__weak修饰符的变量obj。即变量obj持有对持有对象的弱引用。因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会被立即释放。如果将对象赋值给附有__strong修饰符的变量之后再赋值给附有__weak修饰符的变量,就不会发生警告了。
{
/**自己生成并持有对象*/
id __strong obj0 = [[NSObject alloc] init];
/** 因为obj0变量为强引用,所以自己持有对象 */
id __weak obj1 = obj0;
/** obj1变量持有生成对象的弱引用 */
}
/*
*因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象。
*因为对象的所有者不存在,所以废弃该对象。
*/
因为带__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];
/**因为obj0变量为强引用,所以自己持有对象*/
obj1 = obj0;
/**obj1持有对象的弱引用*/
NSLog(@"A: %@",obj1);
/** 输出obj1变量持有的弱引用的对象*/
}
/*
*因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象。
*因为对象无持有者,所以废弃该对象。
*废弃对象的同时,持有该对象弱引用的obj1变量的弱引用失效,nil赋值给obj1.
*/
NSLog(@"B: %@",obj1);
/** 输出赋值给obj1变量中的nil */
该代码的运行结果为:
2017-12-05 20:13:28.458858+0800 ImageOrientation[6316:1604800] A: <NSObject: 0x604000207710>
2017-12-05 20:13:30.112086+0800 ImageOrientation[6316:1604800] B: (null)
以上,使用__weak修饰符可以避免循环引用。通过检查__weak修饰符的变量是否为nil,可以判断被赋值的对象是否以废弃。
__unsafe_unretained 修饰符
__unsafe_unretained修饰符的变量部署与编译器的内存管理对象。(ARC式的内存管理式编译器的工作)
id __unsafe_unretained obj = [[NSObject alloc] init];
如果运行以上代码,编译器会发出警告。虽然使用了unsafe的变量,但是编译器并不会忽略。
Assigning retained object to unsafe_unretained variable; object will be released after assignment
附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即释放。
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变量表示的对象已经被废弃(悬垂指针)
* 指向曾经存在的对象,但该对象已经不再存在了,此类指针称为悬垂指针。结果未定义,往往导致程序错误,而且难以检测。
* 错误访问
*/
该代码的运行结果为:
2017-12-06 08:45:54.005966+0800 ImageOrientation[6859:1736666] A: <NSObject: 0x604000011f00>
运行到NSLog(@"B: %@",obj1)时crash:Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
__autoreleasing 修饰符
ARC有效时,不能使用autorelease方法,也不能使用NSAutoreleasePool类。虽然autorelease无法直接使用,但是autorelease功能是起作用的。
ARC无效时代码如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autoreleae];
[pool drain];
ARC有效时,代码可以写成下面这样:
@autoreleaepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
指定“@autoreleasepool块”来代替“NSAutoreleasePool类对象生成,持有以及废弃”这一范围。
另外,ARC有效时,要通过将对象赋值给附加了 __autoreleaing修饰符的变量来代替调用autorelease方法。对象赋值给附有__autoreleasing修饰符的变量等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool。
也就是说可以理解为,在ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法。
一般来说,不会显示地附加__autoreleasing修饰符。在取得非自己生成并持有的对象时,索然可以使用alloc/new/copy/mutableCopy以外的方法来取得对象,但该对象已经被注册到了autoreleasepool。这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool。(init方法返回值的对象不注册到autoreleasepool)
@autoreleasepool{
/** 取得非自己生成并持有的对象*/
id __strong obj = [MutableArray array];
/*
* 因为变量obj为强引用,所以自己持有对象。
* 并且该对象由编译器判断其方法名后自动注册到autoreleasepool
*/
}
/*
* 因为变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象。
* 同时,随着@autoreleasepool块的结束,注册到autoreleasepool中的所有对象被自动释放。
* 因为对象的所有者不存在,所以废弃对象。
*/
访问附有__weak修饰符的变量时,必须访问注册到autoreleasepool的对象。
id __weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
等同于
id __weak obj1 = obj0;
id __autoreleasing temp = obj1;
NSLog(@"class = %@",[temp class]);
因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果要把访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存爱。因此,在使用附有__weak修饰符的变量时就必定要shying注册到autoreleasepool中的对象。
同样地,id的指针或者对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。
ARC 规则
在ARC有效的情况下编译源代码,必须遵守以下规则:
-
不能使用retain/release/retainCount/autorelease
-
不能使用NSAllocateObject/NSDeallocateObject
-
必须遵守内存管理的方法命名规则
-
不要显示调用dealloc
-
使用@autoreleasepool块代替NSAutoreleasePool
-
不能使用区域(NSZone)
-
对象型变量不能作为C语言结构体(struct/union)的成员
-
显示转换"id"和"void"
不能使用retain/release/retainCount/autorelease
内存管理是编译器的工作,因为没有必要使用内存管理的方法(retain/release/retainCount/autorelease)。只能在ARC无效且手动进行内存管理时才能使用。
不能使用NSAllocateObject/NSDeallocateObject
一般通过调用NSObject类的alloc类方法来生成并持有OC对象。alloc类方法实际上是通过直接调用NSAllocateObject函数来生成并持有对象的。
须遵守内存管理的方法命名规则
在ARC无效时,用于对象生成/持有的方法必须遵循以下命名规则:以alloc/new/copy/mutablCopy开始的方法在返回对象时,必须返回给调用方所应当持有的对象。
在ARC有效时,除了上述规则外,增加init规则。
以init开始的方法的规则要比alloc/new/copy/mutableCopy更严格。该方法必须是实例方法,并且必须要返回对象。返回的对象应该为id类型或者该方法声明类的对象类型。
id obj = [[NSObject alloc] init];
如上所示,init方法会初始化alloc方法返回的对象,然后原封不动地返还给调用方。
注:initialize不包含在上述命名规则里。
不要显式调用dealloc
无论ARC是否有效,只要对象的所有者都不持有该对象,该对象就被废弃。对象被废弃时,无论ARC是否有效,都会调用对象的dealloc方法。在ARC无效时,必须调用
[super dealloc]
。ARC有效时会遵循无法显式调用dealloc这一规则,ARC对此会自动进行处理,dealloc中只需技术废弃对象时所必须的处理,比如删除已注册的代理或者观察者对象。
使用@autoreleasepool块代替NSAutoreleasePoll
不能使用区域(NSZone)
对象型变量不能作为C语言结构体的成员
struct Data{
NSMutableArray * array;
}
编译后
error:ARC forbids Objective-C objects in struct
因为C语言的规约上没有方法来管理结构体成员的生存周期。
要把对象型变量假如到结构体成员中,可强制转换为void*或者是附加__unsafe_unretained修饰符。(附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象)
显式转换id和void*
ARC无效时,id变量和void*变量相互赋值没有问题,但是ARC有效时则会引起编译错误。
在ARC有效时,id型或对象型变量赋值给void*或者逆向赋值时都需要进行特定的转换。如果想单纯地赋值,可以使用
__bridige
转换。
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
但是转换为void*的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。
__bridge转换还有其他两种:__bridge_retained转换和__bridge_transfer转换
__bridge_retained转换可使要转换赋值的变量也持有所赋值的对象。__bridge_retained转换与retain相类似。
void *p = 0;
{
id obj = [[NSObject alloc] init];
p = (__bridge_retained void *)obj;
}
NSLog(@"class = %@"