《Objective-C高级编程》ARC规则

Snip20171212_25.png
1.1 概要

实际上“引用计数式内存管理”的本质部分在ARC中并没有改变。就像“字段引用计数”这个名词表示的那样,ARC只是自动地帮助我们处理“引用计数”的相关部分。

设置ARC有效的编译方法如下所示
  • 使用clang(LLVM编译器)3.0或以上版本
  • 指定编译器属性为"-fobjc-arc"
    Xcode4.2 默认设定为对所有的文件ARC有效
1.2 内存管理的思考方式

引用计数式内存管理的思考方式就是思考ARC所引起的变化

  • 自己生成的对象,自己所持有
  • 非自己生成的对象,自己也能持有
  • 自己持有的对象不再需要时释放
  • 非自己持有的对象无法释放
1.3 所有权修饰符

ARC有效时,id类型和对象类型同C语言器类型不同,其类型上必须加所有权修饰符。所有权修饰符一共有四种。

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

__strong修饰符是id类型和对象类型默认的所有权修饰符。

id obj =[ [NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];

id和对象类型在没有明确指定所有权修饰符时,默认为__strong修饰符,上面两行代码相同。

  1. 附有__strong修饰符的变量obj在超出其变量作用域时,即在改变量被废弃时,会释放其被赋予的对象。
  2. __strong修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确地管理其对象的所有者。
1.3.2 __weak修饰符

循环引用容易发生内存泄漏。所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在

  1. __weak修饰符与__strong修饰符相反。弱引用不能持有对象实例
    id __weak obj1 = nil;
    {
        id __strong obj0 = [[NSObject alloc] init];
        obj1 = obj0;
        NSLog(@"obj1=%@",obj1);
    }
    
    NSLog(@"obj1=%@",obj1);
执行结果:
obj1=<NSObject: 0x604000002010>
obj1=(null)
  1. __weak修饰符另一个优点,在持有对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态。

使用__weak修饰符可以避免循环引用。通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已经被废弃。

1.3.3 __unsafe_unretained修饰符

__unsafe_unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。这一点在使用时要注意。

  1. 附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。
  2. 在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_unretained修饰符来替代__weak修饰符。赋值给附有__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用程序会奔溃
1.3.4 __autoreleasing 修饰符

在ARC有效时,用@autoreleasepool块替代NSAtoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法。

Snip20171209_14.png

为什么在访问附有__weak修饰符的变量时必须访问注册到autoreleasepool的对象?因为__weak修饰符只持有对象的弱引用,而在访问对象的过程中,该对象有可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么@autoreleasepool块结束之前都能确保该对象存在。因此,在使用附有__weak修饰符的变量时必定要使用注册到autoreleasepool中的对象。
专栏 __strong 修饰符/__weak 修饰符
附有__strong修饰符,__weak修饰符的变量类似有C++中智能指针std::shared_ptr和std::weak_ptr。std::shared_ptr可通过引用计数来持有C++类实例,std::weak_ptr可避免循环引用。在不得不使用没有__strong 修饰符/__weak 修饰符的C++时,强烈推荐使用这两种智能指针。

2.1 规则

在ARC有效的情况下编译源代码,必须遵守一定的规则。

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 必须遵守内存管理的方法命名规则
  • 不要显示调用dealloc
  • 使用@autoreleaepool块替代NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象型变量不能作为C语言结构体(struct/union)的成员
  • 显示转换“id”和“void”
2.1.1 不能使用retain/release/retainCount/autorelease

内存管理是编译器的工作,因此没有必要使用内存管理的方法。

设置ARC有效时,如果编译器使用了这些方法的源代码,会报错
设置ARC有效时,禁止再次键入retain或者是release代码
只能在ARC无效并且手动进行内存管理时使用retain/release/retainCount/autolease方法

2.1.2 不能使用NSAllocateObject/NSDeallocateObject

在ARC有效时,禁止使用NSAllocateObject函数,如果使用会引起编译器错误

2.1.3 必须遵守内存管理的方法命名规则

在ARC无效时,用于对象生成/持有的方法必须遵守以下的命名规则

  • alloc
  • new
  • copy
  • mutableCopy
2.1.4 不要显示调用dealloc

无论ARC是否有效,只要对象的所有者都不持有该对象,该对象就被废弃。对象被废弃时,不管ARC是否有效,都会调用对象的dealloc方法。

2.1.5 对象型变量不能作为C语言结构体的成员

C语言的结构体(struct或union)成员中,如果存在Objective-C对象型变量,会引起编译器错误。

2.1.6 显示转换id和void *

在ARC有效时,将id变量强制转换void *变量会引起编译器错误

id obj = [[NSObject alloc] init];
void *p = obj;

更进一步,将void *变量赋值给id变量,调用起实例方法,编译器会报错

id o = p;
[o release];

id型或对象型变量赋值给void *或者逆向赋值时都需要进行特定的转换。如果只想单纯地赋值,额可以使用"__bridge"转换。

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
专栏 Objective-C对象与Core Foundation对象
  1. Core Foundation对象主要使用在用C语言编写的Core Foundation框架中,并使用引用计数的对象。在ARC无效时,Core Foundation框架中的retain/release分别是CFRetain/CFRelease。
  2. Core Foundation对象与Objective-C对象的区别很小,不同之处只在于是由哪一个框架所生成的。无论是由哪种框架生成的对象,一旦生成后,便能在不同的框架中使用。Foundation框架的API生成并持有的对象可以用Core Foundation框架的API释放。反过来也可以。
  3. 因为Core Foundation对象与Objective-C对象没有区别。所以在ARC无效时,只用简单的C语言的转换也能实现互换。另外这种转换不需要使用额外的CPU资源。因此被称为“免费桥”。
3.1 属性

当ARC有效时,以下可作为这种属性声明中使用的属性来用

属性声明的属性 所有权修饰符
assign __unsafe_unretained修饰符
copy __strong 修饰符(但是负责的是被复制的对象)
retain __strong修饰符
strong __strong修饰符
unsafe_unretained __unsafe_unretained修饰符
weak __weak修饰符

在声明类成员变量时,如果同属性声明中的属性不一致会引起编译错误。如下例

@property(nonatomic, weak) id obj; // 编译器报错
@property(nonatomic, weak) id __weak obj;  // 正确,附加__weak修饰符
@property(nonatomic, strong) id obj;  // 正确,使用strong
4.1 数组

如下例代码,将nil代入到malloc函数所分配的数组各元素中来初始化是非常危险的。

arrar = (id __strong *)malloc(sizeof(id) * entries);
for (NSUInteger i = 0; i < entries; i++)
  array[i] = nil; 

这是因为由malloc函数分配的内存区域中没有被初始化为0,因此nil会被赋值给附有__strong修饰符的并被赋值了随机的变量中,从而释放一个不存在的对象。在分配内存时推荐使用calloc函数。
像这样,通过calloc函数分配的动态数组就能完全像静态数组一样使用。

array[0] = [[NSObject alloc] init];

但是,在动态数组中操作附有__strong修饰符的变量与静态数组有很大差异,需要自己释放所有的元素。
如以下源代码所示,在只是简单地用free函数废弃了数组用内存块的情况下,数组各元素所赋值的对象不能再次释放,从而引起内存泄漏。

free(array);

这是因为在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,从而动态数组中,编译器不能确定数组的生存周期,所以无从处理。如以下源代码所示,一定要将nil赋值给所有元素中,使得元素所赋值对象的强引用失效,从而释放那些对象。在此之后,使用free函数废弃内存块。

for (NSUInteger i = 0; i < entries; i++)
    array[i] = nil;
free(array);

同初始化时的注意事项相反,即使用memset等函数将内存填充为0也不会释放所赋值的对象。这非常危险,只会引起内存泄漏。对于编译器,必须明确地使用赋值给附有__strong修饰符变量的源代码,所以请注意,必须将nil赋值给所有数组元素。

使用memcpy函数拷贝数组元素以及realloc函数重新分配内存块也会有危险。由于数组元素所赋值的对象有可能被保留在内存中或是重复被废弃,所以这两个函数也禁止使用。

5.1 ARC的实现
5.1.1 __strong 修饰符
Snip20171213_29.png
5.1.2 __weak修饰符
  • 若附有__weak修饰符的变量所引用的对象呗废弃,则将nil赋值给该变量
  • 使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象。
5.1.3 __autoreleasing修饰符

将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法。

6.1 引用计数
uintptr_t _objc_rootRetainCount(id obj)

_objc_rootRetainCount函数可获取指定对象的引用计数值。如下例

id __strong obj = [[NSObject alloc] init];
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));

不使用__autoreleasing修饰符,仅使用附有__weak声明的变量也能将引用对象注册到autoreleasepool中。

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

推荐阅读更多精彩内容