内存管理

MRC 与 ARC

Objective-C中提供了两种内存管理机制:MRC(MannulReference Counting)和 ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来满足不同的需求。现在苹果推荐使用 ARC 来进行内存管理。

MRC

对象操作的四个类别

操作对象 OC中对应的方法 对应的retainCount变化
持有并生成对象 alloc/new/mutableCopy等 +1
持有对象 retain +1
释放对象 release. -1
废弃对象 dealloc

注意
1 这些对象操作的方法并不包含在OC中,而是包含在cocoa框架下的Foundation框架中。从iOS7开始,这些方法被移到Runtime当中,可以objc4-680 NSObject.h 找到
2 对象的 reatinCount 属性并没有实际上的参考价值,参考苹果官方文档《Practical Memory Management》.

四个法则

  1. 自己生成的对象,自己持有
  2. 非自己生成的对象,自己也能持有
  3. 不在需要自己持有的对象的时候,释放
  4. 非自己持有的对象无需释放

autorelease使得对象在超出生命周期后能正确的释放(通过调用release方法)调用release后对象会被立即释放,而调用outorelease后对象不会立即释放,而是注册到autoreleasepool中,经过一段时间后pool结束,此时调用release方法,对象被释放。

在MRC的内存管理模式下,对变量的相关的方法有:reatinreleaseautorelease。reatin和release方法操作是引用计数,当引用计数为0时,便自动释放内存。并且可以用NSAutoreleasePool对象,对加入自动释放池(autorelease调用)的变量进行管理,当drain时回收内存

ARC

ARC是苹果引入的自动内存管理机制。会根据引用计数自动监测对象的生命周期,实现方式是编译时期自动在已有代码中合适的内存管理代码以以及在Runtime做一些优化。

变量标识符

  1. __strong
  2. __weak
  3. ___unsafe_unretained
  4. __autoreleasing

__strong是默认使用标识符 只要还有一个强指针指向某个对象,这个对象就会一直存活

__weak 声明这个引用不会保存被引用对象的存活,如果对象没有强引用了,弱引用会被置为nil

__unsafe_unretained声明这个引用不会保存引用对象的存活,如果对象没有强引用了,它不会被置为nil。如果它引用的对象被回收掉了,该指针就变成野指针。

__autoreleasing 用于标识使用引用传值的参数(id*),在函数返回时会被自动释放掉。

变量标识用法如下

1
Number* __strong num = [[Number alloc] init];

注意 __strong 的位置应该放到 * 和变量名中间,放到其它位置严格上说不正确的,但编译不会报错

属性标识符

assign 表明setter仅仅是一个简单的赋值操作,通常用于基本数据类型。

strong表明属性定义一个拥有者关系。当给属性设定一个新值的时候,首先这个值进行retain,旧值进行release,然后进行赋值操作

weak表明属性定义一个非拥有者关系,当给属性定义一个新值的时候,这个值不会进行retain,旧值也不会进行release,而是进行类似assign的操作。不过当属性指向的对象被销毁时,该属性会被置为nil

unsafe_unreatained 的语义和assign类似,不过是用于对象类型。表示一个非拥有(unretained)的,同时也不会在对象销毁时置nil的(unsafe)关系

copy类似strong,不过赋值是进行copy操作不是retain操作。通常在需要保留某个可变对象(NSString最常见),并且防止它被意外的串改。

unsafe_unretained 的用处
unsafe_unretained 差不多是实际使用最少的一个标识符了,在使用中它的用处主要有下面几点:
1 兼容性考虑。iOS4以及之前还没有引入weak,这种情况下表达弱引用的语义只能使用unsafe_unretained。现在这种情况很少使用了。
2 性能考虑。使用weak对性能有一些影响,因此对性能要求高的话可以考虑使用unsafe_unretained 替换weak。

引用循环

当2个对象相互持有对方的强引用,并且这2个对象的引用计数都不是0的时候,便造成了引用循环。要想破除引用循环,可以从以下几点入手

  1. 注意变量作用域,使用autorelease 让编译器来处理引用。
  2. 使用弱引用weak
  3. 当实例变量完成工作后,将其置为nil

AutoRelease Pool

AutoRelease Pool 提供一种可以允许你向一个对象延迟发送release操作的机制。当你想放弃一个对象的所有权,同时又不希望这个对象立马被释放掉(例如在一个方法中返回一个对象时),AutoRelease Pool的作用就显现出来了

所谓延迟发送 release 消息是指,当我们把一个对象标记为autorelease时:

1
NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease];

这个对象的retainCount会+1,但并不会发送release。当这段语句所处的autoreleasepool 进行drain操作时,所标记了autorelease的对象的retainCount会被-1。即release消息发送被延迟到pool释放的时候。

在ARC环境下,苹果引入了@autoreleasepool 语法,不再需要手动调用autorelease 和 drain 等方法。

AutoReleasePool的用处

在ARC下,我们并不需要手动调用autorelease有关的方法,甚至可以完全不知道autorelease的存在,就可以真确的管理好内存。因为Cocos Touch的RunLoop中,每个runloop circle中系统都自动加入AutoReleasePool的创建和释放。

当我们创建和销毁大量对象时,使用手动创建AutoReleasePool 可以有效的避免内存峰值的出现。在这种情况不手动创建的话,外层系统创建的pool会在整个runloop circel结束之后drain,手动创建的话,会在block结束之后进行drain操作。下面是一个普遍的例子:

1
2
3
4
5
6
7
8
 for (int i = 0; i < 100000000; i++)
{
@autoreleasepool
{
NSString* string = @"abc";
NSArray* array = [string componentsSeparatedByString:string];
}
}

如果不使用autoreleasepool,需要在循环结束之后释放100000000个字符串,如果使用的话,则会在每次循环结束的时候都进行release操作。

AutoRelease Pool drain的时机

如上面所说,系统在runloop 中创建的autoreleasepool 会在runloop一个event结束时进行释放操作。我们手动创建的autoreleasepool会在block执行完成之后进行drain操作。需要注意的是:

  • 当block以异常(exception)结束时,pool不会被drain,
  • pool的drain操作会把所用标记为autorelase的对象进行引用计数-1,但并不意味着这个对象一定会被释放掉,我们可以在autorelease pool中手动retain 对象,以延长它的生命周期(在MRC中)。
0%