1.什么是自动引用计数
自动引用计数( ARC, Automatic Reference Counting )是指内存管理中对引用采
取自动计数的技术“在LLVM编译器中设置ARC 为有效状态. 就不用再次输入入retain 或者是release 代码。”
条件:
- 使用Xcode 4.2 或以上版本.
- 使用LLVM 编译器3.0 或以上版本.
- 编译器中设置ARC 为有效.
2.内存管理/引用计数
2.1.内存管理的思考方式
- 自己能持有自己生成的对象或者非自己生成的对象。
- 自己持有对象不需要时要释放。
- 自己无法释放非自己持有的对象。
对象操作 | OC方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy 等 |
持有对象 | retain |
释放对象 | release |
废弃对象 | dealloc |
copy
生成的对象不可变更,mutableCopy
生成的对象可变更。
这些有关Objective-C 内存管理的方法,是包含在Cocoa 框架中的Foundation 框架类库的NSObject 类中。
OC 内存管理中的alloc/retain/release/dealloc
方法分别指代NSObject 类的alloc
类方法、retain/release/dealloc
实例方法。
2.1.1.持有自己生成的对象
以
alloc, new, copy, mutableCopy
开头用驼峰拼写法命名的方法生成的对象只有自己持有。
根据上述规则:
1.allocMyObject, newTbatObject, copyThis, mutableCopyYourObject
方法生成的对象自己持有。
2.allocate, newer, copying, mutableCopyed
开头的方法不会自己持有。
2.1.2.持有非自己生成的对象
通过
retain
方法可以持有非自己生成的对象
1 | id obj = [NSMutableArray array]; //生成对象 |
某个方法生成对象,并将其返还给调用方:
1 | - (id) allovObject |
对象存在,但自己并不持有:
1 | - (id) object |
2.1.3.自己持有的对象不需要时,要释放
通过
release
方法可以释放自己持有的对象
1 | [obj release]; |
2.1.4.无法释放非自己持有的对象
若释放非自己持有的对象,或者某个对象释放之后再次释放,会导致程序崩溃。
2.2.alloc/retain/release/dealloc在GNUstep中的实现
包含NSObject类的Foundation框架不公开。
但是Foundation框架使用的Core Foundation框架,及通过调用Object类进行内存管理部分是公开的。
GNUstep是Cocoa框架的互换框架,两者的行为及实现方式是一样的。
GNUstep的引用计数是通过将引用计数放在内存块头部的变量中实现的。好处是:
1. 代码量少
2. 能统一管理引用计数用的内存块与对象用的内存块
2.2.1.alloc
的实现
1 | + (id) alloc |
1 | struct obj_layout{ |
NSZone是为防止内存碎片化引入的结构,对内存分配区域本身进行多重化管理。
但由于运行时系统中的内存管理本身已极具效率,因此使用NSZone可能导致效率降低及代码复杂。
去掉NSZone后的alloc实现:
1 | struct obj_layout{ |
2.2.2.retainCount
的实现
引用计数可通过retainCount
获取:[obj retainCount]
。
retainCount的实现:
1 | - (NSUInteger) retainCount |
2.2.3.retain
的实现
若retained不超过int最大值,则使retained加1.
1 | - (id) retain |
2.2.4.release
的实现
若retained为0,调用dealloc;否则使retained减1
1 | - (id) release |
2.2.5.dealloc
的实现
将
self
转换为obj_layout,并调用free()
1 | - (void) dealloc |
2.3.alloc/retain/release/dealloc在苹果中的实现
苹果的引用计数是通过散列表(引用计数表)实现的。好处是:
1. 内存块的分配无需考虑头部。
2. 引用计数表中有内存块的地址,可从每个记录追溯到各对象的内存块。
3. 有助于判断各内存块的持有者是否存在。
2.3.1.alloc
的实现
1 | +alloc |
2.3.2.retainCount/retain/release
的实现
先获取对象对应的散列表,然后按照不同操作进行分发
1 | - (NSUInteger) retainCount{ |
1 | int __CFDoExternRefOpreation(uintptr_t op, id obj) |
2.4.autorelease
类似于C语言中的局部变量,若变量超出作用域,自动被废弃。
使用方法:
- 生成并持有NSAutoreleasePool对象;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]
- 调用已分配对象的autorelease方法;
[obj autorelease]
- 废弃NSAutoreleasePool对象。
[pool drain]
2.4.1.autorelease在GNUstep中的实现
调用NSAutoreleasePool的addObject类方法
1 | - (id) autorelease |
2.4.1.1.addObject类方法的实现
1 | + (void) addObject:(id)anObj |
1 | - (void) addObject:(id)anObj |
2.4.1.2.drain方法的实现
对NSAutoreleasePool中的数组中的对象分别调用release,然后调用数组的release。
1 | - (void) drain |
2.4.2.autorelease在苹果中的实现
使用方法与NSAutoreleasePool类似:
1.先生成AutoreleasePoolPage对象,
2.然后调用autorelease,
3.最后废弃AutoreleasePoolPage对象
_objc_autoreleasePoolPrint()
函数可用来调试查看AutoreleasePoolPage中对象的情况
1 | //生成AutoreleasePoolPage对象 |
3.ARC规则
可对每个文件分别指定是否使用ARC,Xcide4.2默认对所有文件使用ARC
设置ARC有效的编译方法:
1. 使用clang(LLVM编译器)3.0或以上版本
2. 指定编译器属性为“-fobjc-arc”
3.1.所有权修饰符
ARC有效时,必须对id类型或其他对象类型附加所有权修饰符:
1. __strong
2. __weak
3. __unsafe_unretained
4. __autoreleasing
3.1.1.__strong修饰符
默认的修饰符。(不需要加上该修饰符即可实现“不必再次键入retain或者release”的目标)
附有__strong修饰符的obj,在超出其作用域时(即被废弃时),会释放该obj。
附有__strong修饰符的变量之间可以相互赋值。
无需额外工作即可直接用于类成员变量和方法参数。
能保证将附有该修饰符的变量初始化为nil:”id __strong obj;”等同于”id __strong obj = nil;”
无法解决“循环引用”的问题,容易导致内存泄漏。
3.1.2.__weak修饰符
- 弱引用不持有对象实例
- 用法:将strong修饰的变量赋值给weak修饰的变量
- 优点:
a. 弱引用指向的对象被废弃时,弱引用自动失效并被赋nil
b. 使用附有__weak 修饰符的变量,即是使用注册到autoreleasepool 中的对象 - 弱引用仅支持IOS5及OS X Lion以上版本
3.1.3.__unsafe_unretained修饰符
- 不安全的所有权修饰符,不能保证修饰的变量指向的对象是有效的。在指向的对象被废弃时,该变量仍指向原地址,造成悬垂指针。因此在使用该修饰符修饰的变量时,必须保证指向的对象确实存在。
- 该修饰符修饰的变量,不属于编译器的内存管理对象
- 在不支持弱引用的版本中可用该修饰符
3.1.4.__autoreleasing修饰符
- 在ARC无效时,要使用autorelease功能,必须使用NSAutoreleasePool或者AutoreleasePoolPage;在ARC有效时,要使用autorelease功能,可直接用__autoreleaseing修饰变量,然后用
@autoreleasepool{···}
块包裹该变量的使用。
- 通常不需要显式地使用该修饰符:在@autoreleasepool块中,
- 编译器会将不是以
alloc/new/copy/mutableCopy
开头的方法所返回的对象自动注册到autoreleasePool中 - __weak修饰的对象会被注册到autoreleasePool中
- id的指针(
id *obj
)或者其他对象的指针(NSObject **obj
)会被隐式的加上__autoreleasingid:是动态类型,相当于一个指针变量。
id obj
==>>NSObject *obj
指针变量:指向其他对象的对象。类似与一个两节的盒子,第一节是该指针变量的地址,第二节是指向对象的内存地址。例如Person *p = [[Person alloc] init]
中p
就是指针变量,*p
获得p指向的对象。
- 编译器会将不是以
3.1.5所有权修饰符总结
对象指针赋值时,要保证所有权修饰符一致
除__unsafe_unretained
修饰符之外的__strong/__weak/__autoreleasing
修饰符均能保证其指定的变量初始化为nil
隐式的所有权推导:id obj
==>> id __strong obj
id *obj
==>> id __autoreleasing obj
NSObject *obj
==>> NSObject __strong *obj
NSObject **obj
==>> NSObject * __autoreleasing *obj
下述代码会编译报错:
1 | NSError *err = nil; //err是__strong引用 |
下述代码不会报错:
1 | NSError *err = nil; |
@autoreleasepool可嵌套使用
ARC无效时也可使用@autoreleasepool,因此无论ARC是否有效,均应使用@autoreleasepool
无论ARC是否有效,objc_autoreleasePoolPrint()均可使用
3.2.规则
在ARC有效时必须遵守以下规则
- 不使用
retain/release/retainCount/autorelease
- 不使用
NSAllocateObject/NSDeallocateObject
- 不使用
NSZone
- 对象型变量不能作为结构体(struct/union)的成员
- 显示转换
id
与void*
- 遵守内存管理的方法命名规则
- 使用
@autoreleasepool
代替NSAutoreleasePool - 不显式调用
dealloc
3.2.1.不使用retain/release/retainCount/autorelease
3.2.2.不使用NSAllocateObject/NSDeallocateObject
这两个是OC底层实现对象的创建与销毁
3.2.3.不使用NSZone
3.2.4.对象型变量不能作为结构体(struct/union)的成员
C语言中没有方法来管理结构体成员的生存周期。
可将对象型变量强转为void*
放入结构体中。
3.2.5.显示转换id与void*
- 使用
__bridge
转换:单纯地赋值(__bridge void *)obj
- 使用
__bridge_retained
:可使要转换的变量也持有所赋值的对象 - 使用
__bridge_transfer
:被转换的变量所持有的对象在赋值之后及被释放
这些转换多发生在OC与Core Foundation对象之间的转换。
OC与Core Foundation对象之间的转换可用以下函数来处理:
1 | CFTypeRef CFBridgeingRetain(id id_obj){ //oc对象转cf对象 |
3.2.6.遵守内存管理的方法命名规则
- 以alloc/new/copy/mutableCopy开头的方法在返回对象时,必须给调用方持有该对象。
- init开头的方法必须是实例方法,且必须返回对象,且返回的对象不注册到autoreleasePool中。只是用来对alloc方法返回的对象进行初始化并返回该对象。
3.2.7.使用@autoreleasepool代替NSAutoreleasePool
3.2.8.不显式调用dealloc
- 无论ARC是否有效,只有对象的所有者都不持有该对象,编译器就要调用dealloc将该对象销毁。
- dealloc中通过free来释放内存
- dealloc大多数情况适用于删除已注册的代理或观察对象。
- ARC无效时要用
[super dealloc]
,ARC有效时无法显示调用。
3.3.属性
- 定义:属性是类中描述对象的抽象概念
- 使用:
@property (nonatomic, strong) NSString *name;
此时name
就是一个属性,编译器会自动创建其对应的实例变量(成员变量)_name
,之后在操作self.name
时就相当于操作_name
. - 引入属性的原因:编译器遇到
@property
就会为这个属性添加setter/getter
方法。
属性声明的属性 | 所有权修饰符 |
---|---|
assign |
__unsafe_unretained |
copy |
__strong (但赋值的是被赋值的对象) |
retain |
__strong |
strong |
__strong |
unsafe_unretained |
__unsafe_unretained |
weak |
__weak |
3.4.数组
- 将修饰符修饰的变量作为静态数组时,与对象是一样的,超出作用域时会被释放
- 将__strong和__weak修饰符修饰的变量作为动态数组时,要用指针,需要手动分配内存并初始化(建议使用
calloc
或者malloc + memset
),需要手动释放所有元素。 - __autoreleasing不要使用动态数组。
- __unsafe_unretained只能作为指针类型使用。
4.ARC的实现
4.1.__strong修饰符
对于alloc/new/copy/mutableCopy开头的自己生成并持有的方法,在创建对象时,会调用alloc
和init
方法,然后在作用域结束时插入release
。
1 | { |
对于非自己生成并持有的方法创建对象时,编译器会调用objc_retainAuroreleaseReturnValue
函数来持有注册到autoreleasepool中的对象,然后在作用域结束时调用release。
1 | { |
objc_auroreleaseReturnValue
函数用来返回注册到autoreleasepool中的对象,一般与objc_retainAuroreleaseReturnValue是成对出现的。
若在objc_auroreleaseReturnValue之后紧接着出现objc_retainAuroreleaseReturnValue函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法调用方,达到了最优化。
4.2.__weak修饰符
__weak在被废弃时会赋值为nil
使用__weak变量即使用autoreleasepool 中的对象
用objc_initWeak
初始化,在作用域结束时调用objc_destroyWeak
。
1 | { |
objc_initWeak(&obj1, obj);
==>> obj1 = 0; objc_storeWeak(&obj1, obj);
objc_destroyWeak(&obj1, obj);
==>> objc_storeWeak(&obj1, 0);
objc_storeWeak
将第二参数的地址作为键值,把第一参数的地址注册到weak表(用散列表实现)中。
4.2.1.__weak在被废弃时会赋值为nil
- objc_release
- 因为引用计数为0,所以执行dealloc
- _objc_rootDealloc
- object_dispose
- objc destructlnstance
- objc clear_ deallocating
1)从weak 表中获取废弃对象的地址为键值的记录。
2)将包含在记录中的所有附有__weak 修饰符变最的地址,赋值为nil 。(这一功能会使得当有大量弱引用变量时,会消耗CPU资源)
3)从weak 表中删除该记录。
4)从引用计数表中删除废弃对象的地址为键值的记录。
自己生成并持有的对象赋值给__weak变量时,自己无法持有该对象,编译器会发出警告,并将其赋值为nil。
4.2.2.使用__weak变量即使用autoreleasepool 中的对象
1 | { |
如果大量地使用weak变量,注册到autoreleasepool 的对象也会大量增加, 因此在使用附有weak 修饰符的变量时,最好先暂时将weak变量赋值给strong变量,最后再使用strong变量。
当allowsWeakReference/retainWeakReference
实例方法返回NO时,不能使用__weak修饰符。
4.3.__autoreleasing修饰符
将对象赋值给附有autoreleasiag 变量等向于ARC 无效时调用对象的autorelease方法
1 | { |
在alloc/new/copy/mutableCopy 方法群之外的方法中使用注册到autoreleasepool 中的对象:
1 | { |
4.4.引用计数
获取引用计数数值:uintptr_t _objc_rootRetainCount(id obj)
实际上并不能够完全信任该函数取得的数值:
1.对于己释放的对象以及不正确的对象地址,有时也返回“1”。
2.在多线程中使用对象的引用计数数值, 因为存有竞态条件的问题,所以取得的数值不一定完全可信。