iOS内存管理

内存管理原则


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

自己生成的对象,自己持有

使用 alloc/new/copy/mutableCopy 名称开头的方法名意味着自己生成的对象只能自己持有!

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

id obj1 = [NSObject new];

使用NSObject类的alloc类方法就能自己生成并持有对象。指向生成并持有对象的指针被赋给变量obj。另外,使用new类方法也能生成并持有对象,两中方式是完全一致的。

而copy方法则利用基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本,虽说是对象副本,但同alloc、new方法一样。mutableCopy也是如此。

非自己生成的对象,自己也能持有

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

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

源代码中,NSMutableArray类对象被赋值给变量obj,但变量obj自己并不持有该对象,所以使用retain方法可以持有对象。

不再需要自己持有的对象时释放

自己持有的对象,一旦不再需要,持有者有义务释放该对象。

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

// 释放对象
[obj release];

如此,自己生成并持有的对象就通过release方法释放了。需要注意的是,对象一旦释放绝对不可访问。

那么,调用[NSMutableArray array]方法取得的对象存在,但自己不持有对象是如何实现的呢?

- (id)object
{
    // 自己持有对象
    id obj = [[NSObject alloc] init];

    // 自动释放
    [obj autorelease];

    return obj;
}

上例中,使用了autorelease方法。用该方法,可以使取得的对象存在,但自己却不持有对象。autorelease提供这样的功能,使对象在超出指定的生存范围时能够自动并正确地释放(调用release方法)。

调用release方法对象会立即释放。但调用autorelease方法不会立即释放,而是把对象注册到autoreleasepool中,经过一段时间后,pool结束时让里面的对象自动调用release方法,这个后面会详细说明!

无法释放非自己持有的对象

对于用alloc/new/copy/mutableCopy方法生成并持有的对象,或是用retain方法持有的对象,由于持有者是自己,所以在不需要该对象的时候将其释放。而由此以外所得到的对象绝对不能释放,因为释放了非自己所持有的对象就会造成崩溃!

example1:自己生成并持有的对象,在释放完后再次释放

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

    // 释放对象
    [obj release];

    // 释放之后再次释放非自己持有的对象
    // 崩溃信息:访问一块坏内存
    [obj release];

example2:在“取得的对象存在,但自己不持有对象”时释放

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

[obj release];

内存管理的引用计数实现原理


苹果对引用计数的实现是:采用引用计数表管理引用计数(下面是列举的两个好处)

  • 对象用内存块的分配无需考虑内存块头部
  • 引用计数表各记录中存有内存块地址,可从各个记录追溯都各个对象的内存块

所以可知,第二条在调试的时候有很重要的作用。即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有被损坏,就能够确认各内存块的位置。而且在利用工具检测内存泄漏时,引用计数表的各记录也有助于检测各对象的持有者是否存在。

Autorelease

Autorelease的使用

autorelease从名字上来看,意思就是“自动释放”,它类似于C语言中局部变量的特性。当超出其作用域时,对象实例的release实例方法会被调用。

autorelease使用如下:

  • 生成并持有NSAutoreleasePool对象
  • 调用已分配对象的autorelease实例方法
  • 废弃NSAutoreleasePool对象

而且对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。

Autorelease的实现

在GNUstep的源代码中,autorelease的实现为如下代码:

- (id)autorelease
{
    [NSAutoreleasePool addObject:self]; 
}

可以看出,autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法。

当调用NSObject类的autorelease实例方法时,该对象会被追加到正在使用的NSAutoreleasePool对象中的数组里.

所有权修饰符


Objective-C中为了处理对象,可将变量类型定义为id类型或各种对象类型。

  • 对象类型:指向NSObject这样的Objective-C类的指针
  • id类型:用于隐藏对象类型的类名部分,相当于C语言中常用的void *

ARC有效时,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符,一共有四种:

  • __strong
  • __weak
  • __unsafe _unretained
  • __autoreleasing

__strong 修饰符

__strong修饰符是id类型和对象类型默认的所有权修饰符。

// 下面两句代码效果是一样的
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];

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

{
    // 自己生成并持有对象
    // 因为变量obj为强引用,所以自己持有对象
    id __strong obj = [[NSObject alloc] init];

}

/*
 *  因为变量obj超出其作用域,强引用实效。
 *  所以自动释放自己持有的对象,对象的所有者不存在,因此废弃该对象
 */

__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能正确地管理其对象的所有者,而且Objective-C类成员变量,也可以在方法参数上,使用附有__strong修饰符的变量。

所以很明显,__strong修饰符很符合“引用计数式内存管理的思考方式”。“自己生成的对象,自己持有”和“非自己生成的对象自己也能持有”只需通过对带__strong修饰符的变量赋值便可达成。通过废弃带__strong修饰符的变量或者对变量赋值,就可以做到“不再需要自己持有的对象时释放”。而由于不再键入release,所以“非自己持有的对象无法释放”根本就不可能执行。

__weak 修饰

__strong是有一个致命的缺点,那就是容易出现两个对象相互引用(循环引用容易发现内存泄漏)和一个对象自引用,而__weak就可以避免这种现象。

iOS内存管理_第1张图片

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

    // obj1变量持有生成对象的弱引用
    id __weak obj1 = obj0;
}

/**
 *  因为obj0变量超出其作用域,强引用实效,所以自动释放自己的持有的对象
 *  而对象的所有者不存在,所以废弃该对象
 */

带__weak修饰符的变量不持有对象,所以在超出其变量作用域的时候,对象即被释放。

__weak修饰符还有另一个优点。在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效,并且处于nil被赋值的状态。

id __weak obj1 = nil;

{
    id __strong obj0 = [[NSObject alloc] init];

    obj1 = obj0;

    // 输出obj1变量持有的弱引用的对象
    NSLog(@"A: %@", obj1);
}

/**
 *  因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象
 *  对象无持有者,所以废弃该对象
 *
 *  废弃该对象的同时,持有该对象弱引用的obj1变量的弱引用失效,nil赋值给obj1
 */
NSLog(@"B: %@", obj1);

打印结果:
这里写图片描述

因此,使用__weak可以避免循环引用,而且通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。

__unsafe _unretained 修饰符

ARC式的内存管理是编译器的工作,但附有__unsafe _unretained修饰符的变量不属于编译器的内存管理对象。

__unsafe _unretained修饰符和__weak修饰符其实类似,在上面的代码中把修饰符改为__unsafe _unretained后,打印结果就为:

需要注意的是:赋值给附有__unsafe _unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用程序就会崩溃

__autoreleasing 修饰符

ARC有效时,不能使用autorelease方法,也不能使用NSAutoreleasePool类。不过实际上,ARC有效时autorelease功能也是起作用的。

ARC有效时,@autoreleasePool块代替了NSAutoreleasePool类,__autoreleasing修饰符代替了autorelease方法。 而且__autoreleasing修饰符和__strong一样,一般都是非显式的附加。

注意点:编译器会自动检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasePool中,而且根据命名规则,init方法返回值的对象不注册到autoreleasePool中

在ARC有效时,[NSMutableArray array]方法的内部实现如下:

+ (id)array
{
    id obj = [[NSMutableArray alloc] init];

    return obj;
}

由于return使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为函数返回值,编译器会自动将其注册到autoreleasePool。

而且在访问附有__weak修饰符的变量时,实际上必定要访问注册到autoreleasePool的对象。因为__weak修饰符持有对象的弱引用,而在访问引用对象的过程中,该对象有可能会被废弃。所以为了确保该对象存在,就会把要访问的对象注册到autoreleasePool中,那么在@autoreleasePool块结束之前都能让其存在。

ARC规则


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

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 须遵守内存管理的方法命名规则
  • 不要显式调用dealloc
  • 使用@autoreleasepool块代替NSAutoreleasePool
  • 不能使用区域 (NSZone)
  • 对象类型变量不能作为C语言结构体的成员
  • 显式转换id和void *

你可能感兴趣的:(iOS,开发,内存管理,ios,ARC,MRC)