本文来自Objective-C高级编程(iOS与OS X多线程和内存管理)
一、什么是自动引用计数
自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术;苹果官方说明:
在Objective-C中采用Automatic Reference Counting(ARC)机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码,这就降低程序崩溃、内存泄露等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清除目标对象,并能立刻释放那些不再被使用的对象。如此一来,引用程序具有可预防性,且能流畅运行,速度也将大幅提升。
ARC使用条件:
- 使用Xcode 4.2或以上版本;
- 使用LLVM编译器3.0或以上版本;
- 编译器选项中设置ARC为有效;
二、内存管理/引用计数
Objective-C提供了三种内存管理方式:
- ①、manual retain-release(MRR,手动管理);
- ②、automatic reference counting(ARC,自动引用计数);
- ③、garbage collection(垃圾回收);
1、概要
ObjC采用引用计数(reference counting)的技术来进行管理:
- 1)每个对象都有一个关联的整数,称为引用计数器;
- 2)当代码需要使用该对象时,则将对象的引用计数加1;
- 3)当代码结束使用该对象时,则将对象的引用计数减1;
- 4)当引用计数的值变为0时,表示对象没有被任何代码使用,此时对象将被释放。
与之对应的消息发送方法如下:
- 1)当对象被创建(通过alloc、new或copy等方法)时,其引用计数初始值为1;
- 2)給对象发送retain消息,其引用计数加1;
- 3)給对象发送release消息,其引用计数减1;
- 4)当对象引用计数归0时,ObjC給对象发送dealloc消息销毁对象。
2、内存管理的思考方式
- 自己生成的对象,自己持有;
- 非自己生成的对象,自己也能持有;
- 不再需要自己持有的对象时释放;
- 非自己持有的对象无法释放。
对象操作与Objective-C方法的对应
对象操作 | Objective-C方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc方法 |
(1)、自己生成的对象,自己持有
- ①、alloc
id obj = [[NSObject alloc]init];
- ②、new
id obj = [NSObject new];
作用同①。
- ③、copy/mutableCopy
copy方法利用基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。与copy方法类似,mutableCopy方法利用基于NSMutableCopying方法约定,由各类实现mutableCopyWithZone:方法生成并持有对象的副本。两者的区别在于,copy方法生成不可变更的对象,而mutableCopy方法生成可变更对象。
(2)、非自己生成的对象,自己也能持有
//去的非自己生成并持有的对象
id obj = [NSMutableArray array];
//取得的对象存在,但自己不持有对象
[obj retain];
//自己持有对象
(3)、不再需要自己持有的对象时释放
//自己生成并持有对象
id obj = [[NSMutableArray alloc]init];
//自己持有对象
[obj release];
//释放对象
(4)、非自己持有的对象无法释放
//取得的对象存在,但自己不持有对象
id obj = [NSMutableArray array];
//释放对象导致程序崩溃
[obj release];
3、alloc/retain/release/dealloc实现
GNUstep是Cocoa框架的互换框架。
###(1)、alloc
下面是GNUstep类中alloc类方法在NSObject.m源代码中的实现;
/**
* Allocates a new instance of the receiver from the default
* zone, by invoking +allocWithZone: with
* <code>NSDefaultMallocZone()</code> as the zone argument.<br />
* Returns the created instance.
*/
+ (id) alloc
{
return [self allocWithZone: NSDefaultMallocZone()];
}
/**
* This is the basic method to create a new instance. It
* allocates a new instance of the receiver from the specified
* memory zone.
* <p>
* Memory for an instance of the receiver is allocated; a
* pointer to this newly created instance is returned. All
* instance variables are set to 0. No initialization of the
* instance is performed apart from setup to be an instance of
* the correct class: it is your responsibility to initialize the
* instance by calling an appropriate <code>init</code>
* method. If you are not using the garbage collector, it is
* also your responsibility to make sure the returned
* instance is destroyed when you finish using it, by calling
* the <code>release</code> method to destroy the instance
* directly, or by using <code>autorelease</code> and
* autorelease pools.
* </p>
* <p>
* You do not normally need to override this method in
* subclasses, unless you are implementing a class which for
* some reasons silently allocates instances of another class
* (this is typically needed to implement class clusters and
* similar design schemes).
* </p>
* <p>
* If you have turned on debugging of object allocation (by
* calling the <code>GSDebugAllocationActive</code>
* function), this method will also update the various
* debugging counts and monitors of allocated objects, which
* you can access using the <code>GSDebugAllocation...</code>
* functions.
* </p>
*/
+ (id) allocWithZone: (NSZone*)z
{
return NSAllocateObject (self, 0, z);
}
NSAllocateObject函数:
NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone)
#endif
{
id new;
int size;
NSCAssert((!class_isMetaClass(aClass)), @"Bad class for new object");
size = class_getInstanceSize(aClass) + extraBytes + sizeof(struct obj_layout);
if (zone == 0)
{
zone = NSDefaultMallocZone();
}
new = NSZoneMalloc(zone, size);
if (new != nil)
{
memset (new, 0, size);
new = (id)&((obj)new)[1];
object_setClass(new, aClass);
AADD(aClass, new);
}
/* Don't bother doing this in a thread-safe way, because the cost of locking
* will be a lot more than the cost of doing the same call in two threads.
* The returned selector will persist and the runtime will ensure that both
* calls return the same selector, so we don't need to bother doing it
* ourselves.
*/
if (0 == cxx_construct)
{
cxx_construct = sel_registerName(".cxx_construct");
cxx_destruct = sel_registerName(".cxx_destruct");
}
callCXXConstructors(aClass, new);
return new;
}
NSAllocateObject函数通过调用NSZoneMalloc函数来分配存放对象所需的内存空间,之后将内存空间置0,最后返回作为对象而使用的指针。
注:NSZone:它是防止内存碎片而引入的结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高了内存管理的效率;
alloc方法调用struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置0后返回。
struct obj_layout {
char padding[__BIGGEST_ALIGNMENT__ - ((UNP % __BIGGEST_ALIGNMENT__)
? (UNP % __BIGGEST_ALIGNMENT__) : __BIGGEST_ALIGNMENT__)];
NSUInteger retained;
};
typedef struct obj_layout *obj;
4、自动释放池(autorelease)
autorelease就是自动释放,当給一个对象发送autorelease消息时,方法会在未来某个时间給这个对象发送release消息将其释放,在这个时间段内,对象还是可以使用的。
(1)、原理
对象接收到autorelease消息时,它会被添加到了当前的自动释放池中,当自动释放池被销毁时,会給池里所有的对象发送release消息。
(2)、使用方法
①、生成并持有NSAutoreleasePool对象;
- 使用NSAutoreleasePool来创建
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
//这里写代码
[pool release];
- 使用@autoreleasepool创建
@autoreleasepool {
//这里写代码
}
②、调用已分配对象的aurorelease实例方法;
③、废弃NSAutoreleasePool对象;
NSAutoreleasePool对象的生命周期相当于C语言变量的作用域。对于所有调用autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。
在cocoa框架中,程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有和废弃处理。
当我们大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放。有时候会产生内存不足的情况。
我们可以在必要的地方持有,废弃:
for (int i = 0; i < count; ++i) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
***
[pool drain];
}
三、ARC规则
1、概要
(1)、设置ARC有效的编译方法如下:
指定编译器属性为:"-fobjc-arc"。
(2)、设置ARC无效的编译方法如下:
指定编译器属性为:"-fno-objc-arc"。
2、内存管理的思考方式
- 自己生成的对象,自己所持有
- 非自己生成的对象,自己也能持有
- 自己持有的对象不再需要时释放
- 非自己持有的对象无法释放
3、所有权修饰符
Objective-C编程中为了处理对象,可将变量类型定义为id类型或各种对象类型。
所谓对象类型就是指向NSObject这样的Objective-C类的指针,例如"NSObject *"。id类型用于隐藏对象类型的类名部分,相当于C语言中常用的"void *"。
ARC有效时,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符,所有权修饰符一共有4种:
- __strong
- __weak
- __unsage_unretained
- __autoreleasing
(1)、__strong修饰符
___strong修饰符是id类型和对象类型默认的所有权修饰符。 也就是说 代码中的id变量实际上被附加了所有权修饰符。
id和对象类型在没有明确指定所有权修饰符的时候,默认是strong类型的:
//等价
id obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init];
__strong修饰符的变量之间可以相互赋值。
__strong修饰符的变量不仅在变量作用域中,在赋值上也能够正确地管理其对象的所有者。
(2)、__weak修饰词
在引用计数的时候会产生 循环引用 的问题。
上述两种情况都会产生循环引用;
__weak与strong相反,提供弱引用,他不能持有对象实例。
我们为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。
{
id __strong obj1 = [[NSObject alloc] init];
id __weak obj2 = obj1;
}
__weak还有一个优点,在持有某对象的弱引用时,若该对象被抛弃,则此弱引用将自动失效,并处于nil被赋值的状态(空弱应用)。
(3)、__unsafe_unretained修饰符
他是不安全的所有权修饰符。附有__unsafe_unretained修饰符的变量不属于编译器内存管理对象。
他跟__weak一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。
(4)、__autoreleasing修饰符
arc有效的时候,autorelease 和 NSAutoreleasePool 都是不能直接使用的。
我们应该写成:
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
@autoreleasepool来替代NSAutoreleasePool类对象的生成,持有以及废弃。
在arc有效的时候,要通过对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。
@autoreleasepool{
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象, 并且该对象 由编译器判断其方法名后,自动注册到autoreleasepool
}
//因为变量obj超出作用域,强引用失效,自动释放持有的对象,同时随着@autoreleasepool的结束,注册到其中的所有对象被释放。因为对象的所有者不存在,所以废弃。
还有就是__weak修饰的变量,他在被访问的时候,必定会访问注册到autoreleasepool的对象, 因为__weak修饰符只持有对象的弱引用, 而在访问引用对象的过程中,该对象有可能被废弃,如果把要反问的对象注册到autoreleasepool中,那么在autoreleasepool块结束之前都能确保该对象存在;
id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰词,例如:
NSObject* *obj;-->NSObject* __autoreleasing*obj;
NSError **error;-->NSError *__autoreleasing*error的。
赋值给对象指针时,所有权修饰符必须一致
NSError *error = nil;
NSError **pError = &error;
要修改加上__strong修饰符。
NSError *error = nil;
NSError *__strong*pError = &error;
NSError __weak*error = nil;
NSError *__weak*pError = &error;
下面的例子也是正确的:
NSError __strong *error = nil;
NSError **pError = &error;
其实他是被改写了:
NSError __strong*error = nil;
NSError _autoreleasing *tem = error;
NSError **pError = &tem;
4、规则
在ARC有效的情况下编译源代码,必须遵守一定的规则。
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 须遵守内存管理的方法命名规则
- 不要显式调用dealloc
- 使用@autoreleasepool块代替NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结构体(struct/union)的成员
- 显式转换"id"和"void *"
id型或对象型变量赋值给void *或者逆向赋值都需要进行特定的转换。如果单纯地赋值,则可以使用"__bridge转换"
id obj = ------
void *p = (__bridge void*)obj;
id o = (__bridge id)p;
但是转换为void *的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就会因悬挂指针而导致程序崩溃。
__bridge有两种类型,分别是:__bridge_retianed 和 __bridge_transfer。 他们类似于 retian和release
5、属性
当ARC有效时,以下可作为这种属性声明中使用的属性来用:
属性声明的属性 | 所有权修饰符 |
---|---|
assign | __unsafe_unretained修饰符 |
copy | __strong修饰符(但是赋值的是被复制的对象) |
retain | __strong修饰符 |
strong | __strong修饰符 |
unsafe_unretained | __unsafe_unretained修饰符 |
weak | __weak修饰符 |
四、ARC的实现
1、__strong修饰符
最优化问题。
id __strong obj = [NSMutableArray array];
他在编译器中会出现模拟代码 如下:
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
这里有个函数 objc_retainAutoreleasedValue。 还存在一个函数 objc_autoreleasepoolReturnValue。
看一下array函数的转换:
+(id)array {
return [[NSMutableArray alloc]init];
}
转换模拟代码:
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
objc_autoreleaseReturnValue函数返回注册对象到autoreleasepool中。
objc_autoreleaseReturnValue会检查使用该函数的方法或者函数调用方的执行命令列表,如果方法或者函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue函数, 那么将不会将返回的对象注册到autoreleasepool中,而是直接传递到方法或者函数的调用方。
2、__weak修饰符
- 1)、若附有__weak修饰符的变量所引用的对象被抛弃,则将nil赋值给该变量。
- 2)、若用附有__weak修饰符的变量,即是试用注册到autoreleasepool中的对象。
id __weak obj1 = obj;
---->
id obj1;
objc_initWeak(&obj, obj);
objc_destroyWeak(&obj1);
这里就是初始化和结束的过程。
其实就像这样:
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
解释一下: 这里第一个storeWeak函数把第二个参数的赋值对象的地址作为键值,将第一个参数的附有__weak修饰符的变量的地址注册到weak表中。可以注意到,第二次调用的时候第二个参数为0, 也就是说第二个参数为-0的时候,会把变量的地址从weak表中删除。