天天看点

内存管理:自动引用计数(ARC)1. 引用计数式的内存管理2. ARC 规则3. ARC 的实现

内存管理:自动引用计数(ARC)

  • 1. 引用计数式的内存管理
    • 1.1 内存管理方法
    • 1.2 引用计数的内部实现
    • 1.3 autorelease 的使用与实现
      • 1.3.1 使用方法
      • 1.3.2 内部实现
  • 2. ARC 规则
    • 2.1 所有权修饰符
      • 2.1.1 __strong 修饰符
      • 2.1.2 __weak 修饰符
      • 2.1.3 __autoreleasing 修饰符
        • 隐式附加的情形
          • 1. alloc/new/copy/mutableCopy 方法群
          • 2. 访问 __weak 变量
          • 3. 对象指针
      • 2.1.4 __unsafe_unretained 修饰符
    • 2.2 编码规则
    • 2.3 属性
  • 3. ARC 的实现
    • 3.1 __strong 修饰符
    • 3.2 __weak 修饰符
      • 3.2.1 __weak 变量引用的对象被废弃后,将 `nil` 赋给该变量
      • 3.2.2 使用 __weak 变量,即是使用注册到 `autoreleasepool` 中的对象
    • 3.3 __autoreleasing 修饰符

自动引用计数(ARC,Automatic Reference Counting)指内存管理中对引用采用自动计数的技术。在 Objective-C 采用 ARC 机制后,编译器将自动进行内存管理,无需再次键入 retain 或 release 代码,可以降低程序崩溃、内存泄露的风险并减少代码量。

1. 引用计数式的内存管理

1.1 内存管理方法

引用计数式内存管理的四个原则:

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

与 Objective-C 方法对应:

对象操作 Objecitve-C 方法
生成并持有对象 以 alloc/new/copy/mutableCopy 开头的方法(驼峰命名)
持有对象 retain 方法
释放对象 release 方法
废弃对象 dealloc 方法

这些方法包含 NSObject 类中,使用以上方法进行内存管理:

//自己生成并持有对象
id obj = [[NSObject alloc] init]; 


// 取得非自己生成的对象,且自己不持有对象
id obj = [NSMutableArray array];  

// 自己持有对象
[obj retain];


// 释放对象
[obj release];
           

用某个方法生成对象,并将其返回给调用方:

- (id) object{
	id obj = [[NSObject alloc] init];  // 自己生成并持有对象
	[obj autorelease]; // 将该对象注册到 releasepool 中
	return obj;
}
           

使用

autorelease

方法可以使对象在超出指定的生存范围时能够自动并正确的释放。

原理:将对象注册到

autoreleasePool

中,当 pool 废弃时会自动调用

release

释放 pool 中的所有对象。

1.2 引用计数的内部实现

系统在运行时维护一张散列表(引用计数表)来管理引用计数。表的 key 值为内存块地址的散列值,value 为该对象的引用计数。

  • 生成对象:在散列表中创建新的一行,记录新对象的引用计数。
  • 持有对象:查找对象对应的行,该行的值 + 1。
  • 释放对象:查找对象对应的行,该行的值 - 1。如果该行的值已经为 1,则销毁对象。

好处:

  • 对象用内存块的分配无需考虑内存块头部。
  • 引用计数表各记录中存有内存块地址,可从各个记录追溯到哥对象的内存块。即使出现故障导致对象的内存块损坏,只要引用计数表没有被破坏,依然可以找到内存块的位置。

1.3 autorelease 的使用与实现

1.3.1 使用方法

  1. 生成并持有

    NSAutoreleasePool

    对象;
  2. 调用已分配对象的

    autorelease

    实例方法;
  3. 废弃

    NSAutoreleasePool

    对象。

对于所有调用过

autorelease

的对象,在废弃

NSAutoreleasePool

对象时,都将调用

release

方法。

1.3.2 内部实现

[obj autorelease]

本质上是调用

[NSAutoreleasePool addObject: obj]

autoreleasePool

会通过类似数组的数据结构持有对象。

autoreleasePool

的组织形式可以类比为函数调用栈的形式。系统内部由名为

AutorelesePoolPage

的类来维护

autoreleasePool

的栈。当新建一个

autoreleasePool

时,相当于入栈;废弃一个

autoreleasePool

时,会进行出栈。

2. ARC 规则

ARC 的本质部分是与 引用计数式内存管理一样的,只是 ARC 自动地帮助我们处理“引用计数”的相关部分。

2.1 所有权修饰符

Objective-C 中的对象类型必须附加所有权修饰符。

2.1.1 __strong 修饰符

__strong

是对象类型的默认修饰符。

附有

__strong

修饰符的变量在超出其作用域时,会释放其被赋予的对象。

__strong

修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

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

// ARC 无效
{
	id obj = [[NSObject alloc] init];
	...
	[obj release];
}

// 示例
id __strong obj1 = [[NSObject alloc] init]; // obj1 持有 对象 A
id __strong obj2 = [[NSObject alloc] init]; // obj2 持有 对象 B
obj1 = obj2; // obj1 持有对象 B,对象 A 没有所有者被废弃。
           

强引用的对象变量在赋值时会持有对象,通过

__strong

修饰符,可以不必再使用

retain

release

方法。

另外,

__strong

__weak

__autoreleasing

修饰符会保证附有这些修饰符的自动变量初始化为

nil

2.1.2 __weak 修饰符

引用计数式管理中会发生“循环引用”的问题,引起内存泄漏,即应当废弃的对象在超出其生存周期后继续存在。

使用

__weak

修饰符可以避免循环引用,

__weak

修饰符提供弱引用,不能持有对象实例。

在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且赋值为

nil

。通过检查

__weak

的变量可以判断被赋值的对象是否已被废弃。

2.1.3 __autoreleasing 修饰符

ARC无效时,使用

autorelease

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

使用ARC,该代码写成:

@autoreleasepool {
		id __autoreleasing obj = [[NSObject alloc] init];
	}
           

即指定“

@autoreleasingpool

块”来替代“

NSAutoreleasePool

对象生存、持有以及废弃”这一范围。

ARC中,通过将对象赋值给附加了

__autoreleasing

修饰符的变量来替代调用

autorelease

方法,即将对象被注册到

autoreleasepool

隐式附加的情形

通常,很少会需要显式地附加

__autoreleasing

。因为编译器在一些情形下会隐式附加

__autoreleasing

修饰符。

1. alloc/new/copy/mutableCopy 方法群

编译器会检查方法名是否以

alloc/new/copy/mutableCopy

开始,如果不是则自动将返回值的对象注册到

autoreleasepool

。(

init

方法的返回值不会注册到

autoreleasepool

对象所有权状况:

@autoreleasepool {
		id __strong obj = [NSMutableArray array];
		// obj 为强引用,自己持有对象。 并且该对象由编译器判断其方法名后,自动注册到 autoreleasepool
	}
	// obj 超出作用域,强引用失效,自动释放自己持有的对象。
	// @autoreleasepool 块结束,注册到 pool 中的所有对象被自动释放。
	// 对象没有所有者,废弃对象
           
2. 访问 __weak 变量

在访问附有

__weak

修饰符的变量时,实际上是访问注册到

autoreleasepool

的对象。

因为

__weak

只持有对象的弱引用,而在访问的过程中,该对象有可能被废弃,所以把要访问的对象注册到

autoreleasepool

中,在

@autoreleasepool

块结束前都能确保该对象存在。

3. 对象指针

对象的指针在没有显示指定时会自动附上

__autoreleasing

id *obj

/

NSObject **obj

会自动附加

__autoreleasing

变成

id __autoreleasing *obj

/

NSObject * __autoreleasing *obj

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

在使用参数取得对象时,将参数声明为附有

__autoreleasing

修饰符的对象指针类型。

另外,在显式指定

__autoreleasing

修饰符时,对象必须要是自动变量(包括局部变量、函数以及方法参数)。

autoreleasepool

的调试用函数

_objc_autoreleasePoolPrint()

,利用这一函数可调试注册到

autoreleasepool

上的对象。

2.1.4 __unsafe_unretained 修饰符

附有

__unsafe_unretained

修饰符的变量不属于编译器的内存管理对象。

主要用于 iOS4 及 OS X Snow Leopard 的应用程序中。

在使用

__unsafe_unretained

修饰符时,赋值给

__strong

变量时需要确保被赋值的对象确实存在。

2.2 编码规则

在 ARC 有效的情况下必须遵守一定的规则:

  • 不能使用 retain/release/retainCount/autorelease
  • 不能使用 NSAllocateObject/NSDeallocateObject
  • 须遵守内存管理方法的命名规则

    追加了

    init

    的命名规则,该方法必须是实例方法,并且必须要返回该方法声明类的对象类型或超类或子类,该返回对象并不注册到 autoreleasepool 上。
  • 不要显式地调用 dealloc
  • 使用 @autoreleasepoll 块替代 NSAutoreleasePool
  • 不能使用 NSZone
  • 对象型变量不能作为 C 语言结构体的成员

    可强制转换为

    void *

    或附加

    __unsafe_unretained

    修饰符,但要注意内存管理。
  • 显式转换

    id

    void *

    使用

    __bridge

    能够互相转换,多使用在 Objective-C 对象与 Core Foundation 对象之间的互相转换中。

2.3 属性

属性声明的属性与所有权修饰符的对应关系

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

3. ARC 的实现

3.1 __strong 修饰符

赋值给

__strong

修饰符的变量在编译后转换为以下代码:

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

	// 编译器的模拟代码
	id obj = objc_msgSend(NSObject, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	objc_release(obj);
           

在调用

alloc/new/copy/mutableCopy

以外的方法时,编译器会插入

objc_retainAutoreleasedReturnValue(obj)

函数,用于持有从方法中返回的对象:

id __strong obj = [NSMutableArray array];

	// 编译器的模拟代码
	id obj = objc_msgSend(NSMutableArray, @selector(array));
	objc_retainAutoreleasedReturnValue(obj);
	objc_release(obj);
           

与之相对应的函数是

objc_autoreleaseReturnValue

,用于

alloc/new/copy/mutableCopy

以外的返回对象的方法中。即使用

objc_autoreleaseReturnValue(obj)

函数返回注册到

autoreleasepool

中的对象。

+ (id)array {
	return [[NSMutableArray alloc] init];
}
// 编译器的模拟代码
+ (id)array {
	id obj = objc_msgSend(NSMutableArray, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	return objc_autoreleaseReturnValue(obj);
}
           

objc_autoreleaseReturnValue(obj)

函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用

objc_retainAutoreleasedReturnValue(obj)

,那么就不将返回的对象注册到

autoreleasepool

中,而是直接传递给方法或函数的调用方。

通过这两个函数的协作,可以不将对象注册到

autoreleasepool

中而直接传递,优化性能。

3.2 __weak 修饰符

__weak

修饰符主要提供两种功能,以下解析这两种功能的实现。

3.2.1 __weak 变量引用的对象被废弃后,将

nil

赋给该变量

编译器对

__weak

变量赋值的处理:

id __weak weakObj = obj;

	// 编译器的模拟代码
	id weakObj;
	objc_initWeak(&weakObj, obj);
	objc_destroyWeak(&weakObj);
           

通过

objc_initWeak

函数初始化

__weak

变量,在变量作用域结束时通过

objc_destroyWeak

函数释放该变量。

objc_initWeak

函数调用下列代码,将

__weak

变量初始化为 0 后,将赋值的对象作为参数调用

objc_storeWeak

函数。

weakObj = 0;
	objc_storeWeak(&weakObj, obj);
           

objc_destroyWeak

函数将 0 作为参数调用

objc_storeWeak

函数。

objc_storeWeak

函数把传入第二个参数的对象地址作为

key

,将第一个参数即

__weak

变量的地址作为

value

存入 weak 表 中。如果第二个参数为 0,则将该地址从 weak 表 中删除。

weak表与引用计数表相同,都是散列表。

使用 weak 表,将已废弃对象的地址作为

key

值进行检索,即可高速获取对应的

__weak

变量的地址。

另外,由于一个对象可同时赋值给多个

__weak

变量,所以同一个

key

值可注册多个

__weak

变量的地址。

释放对象时的动作
  1. objc_release
  2. 引用计数为 0,执行 dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destrcutInstance
  6. objc_clear_deallocating

对象被废弃时最后会调用

objc_clear_deallocating

函数,逻辑如下:

  1. 从 weak 表 中以废弃对象的地址为

    key

    检索对应的

    value

  2. 将检索到的所有

    value

    __weak

    变量的地址,赋值为

    nil

  3. 从 weak 表 中删除该记录;
  4. 从引用计数表中以废弃对象的地址为

    key

    ,删除对应记录。

通过以上逻辑即可实现

__weak

变量引用的对象被废弃后,将

nil

赋给该变量。

因此,如果大量使用

__weak

变量,会消耗 CPU 资源。所以尽量只在 需要避免循环引用时使用

__weak

修饰符。

3.2.2 使用 __weak 变量,即是使用注册到

autoreleasepool

中的对象

编译器将转换下面代码:

id __weak weakObj = obj;
	NSLog(@"%@", weakObj);

	// 编译器的模拟代码
	id weakObj;
	objc_initWeak(&weakObj,obj);
	id tmp = objc_loadWeakRetained(&weakObj);
	objc_autorelease(tmp);
	NSLog(@"%@", tmp);
	objc_destroyWeak(&weakObj);
           
  • objc_loadWeakRetained

    :取出

    __weak

    变量引用的对象并

    retain

    ;
  • objc_autorelease

    :将对象注册到

    autoreleasepool

    ;

通过以上逻辑,

__weak

变量所引用的对象就被注册到

autoreleasepool

中,所以在

@autoreleasepool

块结束前都可以放心使用。

但是,如果大量使用

__weak

变量,注册到

autoreleasepool

中的对象也会大量增加。因此,在使用

__weak

变量时,最好先暂时赋值给

__strong

修饰符的变量后再使用。

id __weak o = obj;
	NSLog(@"1 %@", o);
	NSLog(@"2 %@", o);
	NSLog(@"3 %@", o);
	//上述代码会将对象在 autoreleasepool 中注册 5 次。

	id tmp = o;
	NSLog(@"1 %@", tmp);
	NSLog(@"2 %@", tmp);
	NSLog(@"3 %@", tmp);
	// 先赋给 __strong 变量,此时仅注册了 1 次。
           

存在一些不支持 __weak 修饰的类,这些类重写了 retain/release 方法并实现独自的引用计数机制,若是将这些类对象赋值给 __weak 变量,一旦编译器检验出来会报出编译错误,且不支持 __weak 的类极为罕见。

当 allowWeakReference / retainWeakReference 返回 NO 时,也不能使用 __weak 修饰符。

3.3 __autoreleasing 修饰符

将对象赋值给

__autoreleasing

变量等同于 ARC 无效时调用对象的

autorelease

方法。

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

	// 编译器的模拟代码
	id pool = objc_autoreleasePoolPush();
	id obj = objc_msgSend(NSObject, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	objc_autorelease(obj);
	objc_autoreleasePoolPop(pool);
           

对于

alloc/new/copy/mutableCopy

方法群之外的方法,改用

objc_retainAutoreleasedReturnValue

函数持有对象。