[精通Objective-C]内存管理

[精通Objective-C]内存管理

参考书籍:《精通Objective-C》【美】 Keith Lee

目录

程序的内存使用情况

Objective-C可执行程序是由(可执行)代码、初始化和未初始化的程序数据、链接信息、重定位信息、局部数据和动态数据构成的。
程序数据包括以静态方式声明的变量和程序常量(即在程序编译时在代码中设置的常数)。可执行代码、程序数据以及链接和重定位信息会以静态方式被分配内存,并在程序的生命周期一直存在。
局部(自动)数据在语句块中声明并且仅在该语句块中有效,当该语句块执行后局部数据不会继续存在。从语法方面讲,Objective-C的符合语句块就是由括号封装的语句集合。自动数据被存储在程序栈中,程序栈通常是在执行程序/线程前就被设定尺寸的内存段。栈用于存储局部变量和调用方法/函数的上下文数据,以及调用完方法后继续执行程序的代码地址,操作系统会自动管理这些内存,这些数据会获得栈中的内存,而分配给这些数据的内存会在它们失效后被释放。
在运行时,Objective-C会将创建的对象(通过NSObject类的alloc方法)存储在动态分配的内存即堆内存中。以动态方式创建对象就意味着需要进行内存管理,因为在堆内存中创建的对象永远不会超出其作用范围。

[精通Objective-C]内存管理

不进行内存管理和错误的内存管理通常会导致以下结果:
内存泄漏:如果程序没有释放不再使用的对象,就会导致出现该问题。如果程序没有使用为其分配的内存,就会浪费内存资源;如果系统继续为程序分配内存并且没有释放这些内存,程序最终会耗尽系统内存。
悬挂指针:如果程序释放了仍在使用的对象,就会导致该问题。如果将来程序尝试访问这些对象,就会出现程序错误。

Objective-C的内存管理是通过引用计数实现的,引用计数是一种通过对象的唯一引用,确定对象是否正在被使用的技术。如果对象的引用计数降到了0,对象就会被视为不再有用,而且运行时系统会释放它的内存。Objective-C开发环境提供了两种内存管理机制:手动管理(MRR)和自动引用技术(ARC)。

手动管理

Objective-C对象是通过指向Objective-C对象内存地址的变量(即指针),以间接方式访问的。对象指针实现了Objective-C对象的访问功能,但是它们本身不能管理所有权。

MRR内存管理基本原则

1.为创建的所有对象设置所有权
2.应使用retain方法获取对象(你尚未拥有)的所有权
3.当不再使用某个对象时,必须放弃其所有权
4.不能放弃不归你所有的对象的所有权

// 对象通过alloc消息创建后,变量atom就拥有了该对象的所有权(原则1)
Atom *atom = [[Atom alloc] init];
// 变量href获取了这个对象的所有权(原则2),不能写成 Atom *href = atom; 这样写的话href没有获取对象的所有权,一旦atom释放了,href就不再指向一个合法的位置,出现指针悬空。
Atom *href = [atom retain];
// 变量atom释放,但href依旧拥有该对象的所有权
[atom release];
// 变量href释放,对象引用计数变为0,运行时系统可以释放对象了
[href release];

也可以通过autorelease方法延迟释放操作

    // 在自动释放池代码块的末尾,调用对象中的释放方法,无需编写调用对象中release方法的具体代码
    @autoreleasepool {
        // 对象被创建并初始化后,会立刻收到autoreleasepool消息,可以确保所有通过autorelease消息创建的对象都会在程序结束前、自动释放池代码快的末尾被释放
        Atom *atom = [[[Atom alloc] init] autorelease];
    }

使用MRR

下面开发一个简单的示例程序,程序共包含3个类:OrderEntry类,OrderItem类和Address类。其中每个OrderEntry对象都拥有一个相应的OrderItem对象和Address对象。

首先需要创建一个新工程,在设置工程存储位置时,取消勾选Source Control复选框,由于Xcode 6之后的版本,工程都是默认使用ARC,完成创建后,需要在下图所示位置进行修改,取消使用ARC。

[精通Objective-C]内存管理

首先是Address类,接口部分不用编辑

Address.m

#import "Address.h"

@implementation Address

-(id)init{
    if ((self = [super init])) {
        NSLog(@"Initializing Address object");
    }
    return self;
}

-(void)dealloc{
    NSLog(@"Deallocating Address object");
    // 对象的dealloc方法调用了对象父类的dealloc方法,从而确保回收对象占用的内存 
    [super dealloc];
}

@end

下面是OrderItem类

OrderItem.h

#import <Foundation/Foundation.h>

@interface OrderItem : NSObject
{
@public NSString *name;
}

-(id) initWithName:(NSString *)itemName;
@end

OrderItem.m

#import "OrderItem.h"

@implementation OrderItem

-(id) initWithName:(NSString *)itemName{
    if ((self = [super init])) {
        NSLog(@"Initializing OrderItem object");
        name = itemName;
        //自定义初始化方法将输入参数赋予实例变量
        //OrderItem对象不再创建输入参数,对象就不再拥有该实例变量的所有权,所以发送retain消息(原则2)
        [name retain];
    }
    return self;
}

-(void) dealloc{
    NSLog(@"Deallocating OrderItem object");
    //释放实例变量name
    [name release];
    [super dealloc];
}

@end

最后是OrderEntry类

OrderEntry.h

#import <Foundation/Foundation.h>
#import "OrderItem.h"
#import "Address.h"

@interface OrderEntry : NSObject
{
@public OrderItem *item;
    NSString *orderId;
    Address *shippingAddress;
}

-(id) initWithId:(NSString *)oid;
@end

OrderEntry.m

#import "OrderEntry.h"

@implementation OrderEntry

-(id) initWithId:(NSString *)oid{
    if ((self = [super init])) {
        NSLog(@"Initializing OrderEntry object");
        orderId = oid;
        //向变量orderId发送一条retain消息(原因同OrderItem中的name变量)
        [orderId retain];
        //创建并初始化OrderItem和Address对象,所以OrderEntry拥有它们的所有权,并负责释放
        item = [[OrderItem alloc] initWithName:@"Doodle"];
        shippingAddress = [[Address alloc] init];
    }
    return self;
}

-(void) dealloc{
    NSLog(@"Deallocation OrderEntry object");
    //释放对象拥有所有权的实例变量和对象
    [item release];
    [orderId release];
    [shippingAddress release];
    [super dealloc];
}

@end

在main.m中测试

#import <Foundation/Foundation.h>
#import "OrderEntry.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建手动释放的OrderEntry对象
        NSString *orderId = [[NSString alloc] initWithString:@"A1"];
        OrderEntry *entry = [[OrderEntry alloc] initWithId:orderId];

        //释放orderId变量,但OrderEntry对象仍旧引用着该变量,所以该变量不会被释放
        [orderId release];
        NSLog(@"New order, ID = %@, item = %@", entry->orderId, entry->item->name);

        //必须手动释放OrderEntry对象!!!
        [entry release];

        //创建自动释放池代码块末尾释放的OrderEntry对象
        OrderEntry *autoEntry = [[[OrderEntry alloc] initWithId:@"A2"] autorelease];
        NSLog(@"New order, ID = %@, item = %@", autoEntry->orderId, autoEntry->item->name);
    }
    return 0;
}

运行结果:

2016-07-01 13:33:09.765 MRR Orders[60798:1376502] Initializing OrderEntry object
2016-07-01 13:33:09.766 MRR Orders[60798:1376502] Initializing OrderItem object
2016-07-01 13:33:09.766 MRR Orders[60798:1376502] Initializing Address object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] New order, ID = A1, item = Doodle
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Deallocation OrderEntry object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Deallocating OrderItem object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Deallocating Address object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Initializing OrderEntry object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Initializing OrderItem object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Initializing Address object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] New order, ID = A2, item = Doodle
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Deallocation OrderEntry object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Deallocating OrderItem object
2016-07-01 13:33:09.767 MRR Orders[60798:1376502] Deallocating Address object

如上述结果所示,所有对象的创建/保留和释放消息都达到了平衡,如果你要确保程序没有内存泄漏,可以使用Product菜单中的Analyze工具进行检测,如果把[orderId release];注释掉就会检测到潜在的内存泄漏问题。

自动引用计数

ARC可以为Objective-C对象和块提供自动内存管理功能,是苹果公司推荐使用的内存管理方式。

ARC规则和约定

1.不能手动编写发送retain、retainCount、release、autorelease和dealloc消息的代码。ARC会在编译时根据需要自动插入这些消息。ARC会在你编写的类(没有编写dealloc方法的情况)中自动创建dealloc方法,释放起拥有的对象并在dealloc方法中调用父类的dealloc方法。
2.不能直接进行id和(void*)类型的互转,ARC只能管理Objective-C对象和块,不能使用C结构中的对象指针。
3.需要使用自动释放池代码块执行由ARC管理的自动释放操作
4.不能使用内存区(NSZone)以及相关的框架函数。
5.为了和非ARC代码合作,不能创建以copy开头的方法和自动声明属性

ARC的声明周期限定符

__strong             //强引用,默认设置,表明任何使用alloc/init消息创建的对象都会在其作用范围内被保留
__weak               //弱引用,表明对象随时可以被释放,只有当对象拥有其它强引用时才有用,对象被释放后,带__weak限定符的变量会被设置为nil
__unsafe_unretained  //与__weak限定符类似,只是对象被释放后,指针不会被设置为空,而是会处于悬挂状态
__autoreleasing      //用于通过引用传递对象

使用ARC

下面开发一个简单的示例程序,程序同样地共包含3个类:OrderEntry类,OrderItem类和Address类。其中每个OrderEntry对象都拥有一个相应的OrderItem对象和Address对象。

由于Xcode 6之后的版本,工程都是默认使用ARC,不再需要其它额外操作。

下面是3个类的实现文件,由于接口文件与使用MRR时完全一样,就不再写出

Address.m

#import "Address.h"

@implementation Address

-(id) init{
    if ((self = [super init])) {
        NSLog(@"Initializing Address object");
    }
    return self;
}

-(void) dealloc{
    NSLog(@"Deallocating Address object");
}

@end

OrderItem.m

#import "OrderItem.h"

@implementation OrderItem

-(id) initWithName:(NSString *)itemName{
    if ((self = [super init])) {
        NSLog(@"Initializing OrderItem object");
        name = itemName;
    }
    return self;
}

-(void) dealloc{
    NSLog(@"Deallocating OrderItem object");
}

@end

OrderEntry.m

#import "OrderEntry.h"

@implementation OrderEntry

-(id) initWithId:(NSString *)oid{
    if ((self = [super init])) {
        NSLog(@"Initializing OrderEntry object");
        orderId = oid;
        item = [[OrderItem alloc] initWithName:@"Doodle"];
        shippingAddress = [[Address alloc] init];
    }
    return self;
}

-(void) dealloc{
    //这里输出加一个orderId,以便后面的验证
    NSLog(@"Deallocation OrderEntry object with ID %@",orderId);
}

@end

可以看到3个类的实现部分与使用MRR时也都大同小异,只是去掉了所有的retain、release消息的发送和[super dealloc]调用父类的dealloc方法(因为ARC会自动调用)。

在main.m中测试

#import <Foundation/Foundation.h>
#import "OrderEntry.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建手动释放的OrderEntry对象
        NSString *a1 = @"A1";
        NSString *orderId = [[NSString alloc] initWithString:a1];
        OrderEntry *entry = [[OrderEntry alloc] initWithId:orderId];

        //将ID设置为nil,以验证ARC保留了该值
        a1 = nil; 
        NSLog(@"New order, ID = %@, item = %@", entry->orderId, entry->item->name);

        //将OrderEntry对象设置为nil,ARC会自动释放它,如果注释掉本行代码,该对象会在自动释放池代码块末尾被释放
        entry = nil;

        //创建自动释放的OrderEntry对象
        OrderEntry *autoEntry = [[OrderEntry alloc] initWithId:@"A2"];
        NSLog(@"New order, ID = %@, item = %@", autoEntry->orderId, autoEntry->item->name);
    }
    return 0;
}

运行结果

2016-07-01 17:03:26.252 ARC Orders[71749:1493022] Initializing OrderEntry object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Initializing OrderItem object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Initializing Address object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] New order, ID = A1, item = Doodle
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Deallocation OrderEntry object with ID A1
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Deallocating Address object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Deallocating OrderItem object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Initializing OrderEntry object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Initializing OrderItem object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Initializing Address object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] New order, ID = A2, item = Doodle
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Deallocation OrderEntry object with ID A2
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Deallocating Address object
2016-07-01 17:03:26.253 ARC Orders[71749:1493022] Deallocating OrderItem object

相比MRR,ARC简化了大量代码

处理循环引用

ARC无法自动处理循环引用,假设OrderEntry对象拥有一个OrderItem实例变量且OrderItem对象拥有一个OrderEntry实例变量,默认情况都是强引用,就会在这两个对象之间造成循环引用。两个对象永远都不会被释放,因而导致内存泄漏。这种问题可以用弱引用解决没被弱引用的对象不属于引用它的对象,从而可以消除循环引用。苹果公司约定,父对象强引用其所有的子对象,子对象弱引用其父对象(如果有必要)。

#import <Foundation/Foundation.h>

@class OrderEntry;
@interface OrderItem : NSObject
{
@public NSString *name;
//使用弱引用
OrderEntry *__weak entry;
}

-(id) initWithName:(NSString *)itemName;
@end
上一篇:[剑指Offer] 第5章课后题详解


下一篇:[精通Objective-C]类,接口,协议与扩展