ARC的内存管理方式


什么是ARC

ARC(自动引用计数)是一个编译期技术,介于垃圾回收(GC)和MRC之间,ARC让程序员不再需要书写retain/release/autorelease语句,runtime会维护一张引用计数表,编译器会在编译期在合适的位置自动给用添加retain/release/autorelease,它的特点是自动引用技术简化了内存管理的难度

ARC内存管理的思考方式:

ARC内存管理的思考方式与MRC一致。

  • 自己生成的对象,自己持有
  • 非自己生成的对象,自己也能持有
  • 自己持有的对象,不再需要时,释放
  • 非自己持有的对象无法释放

ARC代码编写规则

  1. 不能在程序中定义和使用:retain、release、autorelease和retainCount
  2. 使用@autoreleasepool代替NSAutoreleasePool
  3. 不用在dealloc中释放实例变量(可以在dealloc中释放资源),也不需要调用[super dealloc]

所有权修饰符

id类型:用于隐藏对象类型的类名部分,类似于C语言中的void*;
ARC有效时,id类型和对象类型必须加上所有权修饰符:

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

__strong修饰符

__strong修饰符是默认的修饰符

//两种方式是等价的
id obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init]

特性:strong表示强引用,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

  1. 与自己生成并持有对象
//ARC有效
{
    //自己生成,自己持有,obj为强引用,持有变量
    id __strong obj = [[NSObject alloc]init];
    //todo
    .......
}
//超出作用域,强引用失效

//以上代码在ARC无效下等价于
{
    id obj = [[NSObject alloc]init];
    //todo
    ......
    [obj release];
}

2、取得非自己生成但持有的对象

//取得非自己生成的对象,obj为强引用,所以持有该对象
id __strong obj = [NSArray array];

3、附有__strong修饰符的变量可以相互赋值

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

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

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

/*
 * obj0持有obj1赋值的对象B的强引用,对象B的持有者为obj0、obj1
 * obj0被赋值,原先持有的对象A的强引用失效,对象A的所有者不存在,因此对象A废弃
 */
obj0 = obj1;

/*
 * obj2持有obj0赋值的对象B的强引用
 * 对象B的持有者为:obj0、obj1、obj2
 */
obj2 = obj0;

4、附有__strong修饰符的变量作为方法的参数也能正确的管理其对象的所有者

总结:通过__strong修饰符,不必再次键入retain或release,完美地满足了“引用计数式内存管理的思考方式”。

__weak修饰符

__weak修饰符,提供弱引用,弱引用不能持有实例对象,可以避免循环引用

/*
 * 编译器会给警告
 * obj为弱引用,并不持有对象,生成的对象会被立即释放,所以会给警告
 */
id __weak obj = [[NSObject alloc]init];

//obj0强引用持有对象,obj1弱引用不持有对象
id __strong obj0 = [[NSObject alloc]init];
id __weak obj1 = obj0;

注: 在持有某对象的弱引用时,若该对象被废弃,则弱引用自动失效,且处于nil被赋值的状态(空弱引用)

__unsafe_unretained修饰符

__unsafe_unretained是不安全的修饰符,修饰的变量不属于编译器的内存管理对象,同__weak修饰符一样,不能持有实例对象

/*
 * 编译器警告,obj不持有NSObject对象
 */
id __unsafe_unretained obj = [[NSObject alloc]init];

id __unsafe_unretained obj1 = nil;
{
    //obj0强引用NSObject对象
    id __strong obj0 = [[NSObject alloc]init];
    //obj1不持有NSObject对象
    obj1 = obj0;
    NSLog(@"A:%@",obj1);
}
/*
 * obj0变量超出作用域,强引用失效,释放持有变量
 * obj0指向的对象被废弃(悬垂指针),成为僵尸对象
 */
NSLog(@"B:%@",obj1);

执行结果:
A: <NSObject: 0x753e180>
B: <NSObject: 0x753e180>

最后一行NSlog,碰巧正常运行。因为obj0指向的对象已经被废弃,不能被访问

为什么要有__unsafe_unretained修饰符?

在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_retained修饰符来代替__weak__unsafe_unretained修饰的对象赋值给__strong修饰符的变量时,必须保证被赋值对象确实存在。

__autoreleasing修饰符

ARC无效时

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
id obj = [[NSObject alloc]init];
[obj autorelease];
[pool drain];

ARC有效时,代码写为

//ARC指定“@autoreleasepool块”来替代“NSAutoReleasePool类对象生成、持有以及废弃”这一范围
@autoreleasepool {
    //添加__autoreleasing修饰符来代替调用autorelease方法
    id __autoreleasing obj = [[NSObject alloc]init];
}

非显示使用__autorelease的情况

__autoreleasing__strong一样通常都不会显示添加。

  • ARC有效时,在取得非自己生成并持有的对象时,编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回的对象值注册到autoreleasepool中。
@autoreleasepool {
    // 变量为强引用,所以自己持有对象
    //但编译器判断其方法名后,自动将其注册到autoreleasepool
    id __strong obj = [NSMutableArray array];
}
  • alloc/new/copy/mutableCopy开头的非自己生成但持有的方法中定义的对象
+ (id)array
{
    //obj生成并持有对象,强引用
    id obj = [[NSMutableArray alloc]init];
    //强引用在obj超出作用域后会释放对象,但是该对象又作为返回值不能被释放,所以编译器会自动注册到autoreleasepool中
    return obj;
}

//得到的是注册到autoreleasepool中的对象
id obj = [NSMutableArray array];
  • 访问__weak修饰的对象,实际上访问的是注册到autoreleasepool中的对象
id __weak obj1 = obj0;
NSLog(@"class = %@", [obj1 class])

//等价于

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

__weak修饰的对象为弱引用,在访问时可能会随时被废弃,只要把对象注册到autoreleasepool中,就能在@autoreleasepool块结束前确保对象存在

  • id的指针或者NSObject的指针没有显示指定时,会被默认被附上__autoreleasing修饰符
//id的指针
id *obj;
id __autoreleasing *obj;

//对象的指针
NSError **error;
NSError * __autoreleasing *error;

- (BOOL)performOperationWithError:(NSError **)error;

//error为对象指针,所以默认修饰符是__autoreleasing;
- (BOOL)performOperationWithError:(NSError *__autoreleasing *)error {
    *error = [[NSError alloc] initwithDomain:MyApplication code:errorCode userInfo:nil];
    return NO

}

这里- (BOOL)performOperationWithError:(NSError **)error;与除alloc/new/copy/mutableCopy外的其他非自己生成但持有的方法一样,{}里[[NSError alloc] initwithDomain:MyApplication code:errorCode userInfo:nil]生成的对象因为超出作用域后会释放对象,所以编译器会将其放入到autoreleasepool中,所以这里的alloc方法生成的对象(理论上是__strong修饰的对象)能够正常赋值给__autoreleasing修饰的*error

注:赋值给对象指针时,所有权修饰符必须一致

//默认是__strong
NSError *error = nil;
//默认是__autoreleasing
NSError **pError = &error;  //报错

NSError * __autoreleasing *pError1 = &error;    //不报错

//不报错
NSError _strong *error = nil;
BOOL result = [obj performOperationWithError:&error];

上面的error是__strong修饰的,但是performOperationWithError:里的参数是__autoreleasing修饰的,但是没有因修饰符不一致而报错,原因是编译器做了如下转换:

NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];

注:显示指定__autoreleasing时要注意,对象变了要为自动变量(包括局部变量、函数以及方法参数)

总结:

  • __strong__weak以及__autoreleasing修饰的变量初始值默认为nil
  • 自己生成并持有的对象,即以alloc/new/copy/mutableCopy开头的方法生成的对象,编译器默认给变量添加修饰符__strong
  • 非自己生成但持有的对象,编译器默认给变量添加修饰符__autoreleasing
  • 非自己生成但持有对象的方法中返回的值默认是__autoreleasing
  • id指针和对象指针默认是__autoreleasing
  • __weak用于避免循环引用,在对象废弃时,自动降变量置为nil
  • __unsafe_unretained在iOS4中用来替代__weak,但是在对象被废弃时,不会将变量置为nil,因此在将__unsafe_unretained修饰的变量赋值给__strong修饰的变量时要确保对象是否存在
  • 修饰符是ARC时才用的,MRC没有修饰符
  • 修饰符用于实例变量、自动变量等变量的声明,不能用于属性

参考资料:

  • Objective-C高级编程iOS与OS X多线程和内存管理
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容