iOS 自动引用计数

本文来自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表中删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaoxiaobukuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值