[精通Objective-C]内存管理

[精通Objective-C]内存管理

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

目录

  • 精通Objective-C内存管理

    • 目录
    • 程序的内存使用情况
    • 手动管理
      • MRR内存管理基本原则
      • 使用MRR
    • 自动引用计数
      • ARC规则和约定
      • 使用ARC
      • 处理循环引用

程序的内存使用情况

Objective-C可执行程序是由(可执行)代码、初始化和未初始化的程序数据、链接信息、重定位信息、局部数据和动态数据构成的。

程序数据包括以静态方式声明的变量和程序常量(即在程序编译时在代码中设置的常数)。可执行代码、程序数据以及链接和重定位信息会以静态方式被分配内存,并在程序的生命周期一直存在。

局部(自动)数据在语句块中声明并且仅在该语句块中有效,当该语句块执行后局部数据不会继续存在。从语法方面讲,Objective-C的符合语句块就是由括号封装的语句集合。自动数据被存储在程序栈中,程序栈通常是在执行程序/线程前就被设定尺寸的内存段。栈用于存储局部变量和调用方法/函数的上下文数据,以及调用完方法后继续执行程序的代码地址,操作系统会自动管理这些内存,这些数据会获得栈中的内存,而分配给这些数据的内存会在它们失效后被释放。

在运行时,Objective-C会将创建的对象(通过NSObject类的alloc方法)存储在动态分配的内存即堆内存中。以动态方式创建对象就意味着需要进行内存管理,因为在堆内存中创建的对象永远不会超出其作用范围。

不进行内存管理和错误的内存管理通常会导致以下结果:

内存泄漏:如果程序没有释放不再使用的对象,就会导致出现该问题。如果程序没有使用为其分配的内存,就会浪费内存资源;如果系统继续为程序分配内存并且没有释放这些内存,程序最终会耗尽系统内存。

悬挂指针:如果程序释放了仍在使用的对象,就会导致该问题。如果将来程序尝试访问这些对象,就会出现程序错误。

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。

首先是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
时间: 2024-07-30 20:29:30

[精通Objective-C]内存管理的相关文章

Objective C 内存管理[转]

1  配对原则 alloc – release new – release retain - release copy – release 2  new和alloc-init的区别 (1)区别只在于alloc分配内存的时候使用了zone. 这个zone是个什么呢? 它是给对象分配内存的时候,把关联的对象分配到一个相邻的内存区域内,以便于调用时消耗很少的代价,提升了程序处理速度. (2)为什么不推荐使用new 因为若用了new,则初始化方法只能是init.这样,假如你想调用initWithFram

objective C 内存管理及属性方法详解

oc为每个对象提供一个内部计数器,这个计数器跟踪对象的引用计数,当对象被创建或拷贝时,引用计数为1,每次保持对象时,调用retain接口,引用计数加1,如果不需要这个对象时调用release,引用计数减1,当对像的引用计数为0时,系统就会释放掉这块内存,释放对象调用dealloc 当对象包含其他对象时,就得在dealloc中自己释放他们 NSObject是IOS所有类的基类 有两个基本函数,alloc和dealloc alloc类似于C++的new,dealloc类似于delete 当对象的re

iOS核心语言Objective C语言 —— 内存管理

本分享是面向有意向从事iOS开发的伙伴以及苹果产品的发烧友们,或者已经从事了iOS的开发者,想进一步提升者.如果您对iOS开发有极高的兴趣,可以与我一起探讨iOS开发,一起学习,共同进步.如果您是零基础,建议您先翻阅我之前分享的iOS开发分分钟搞定C语言系列,然后在开始Objective C语言的学习,如果您遇到问题也可以与我探讨,另外将无偿分享自己整理出来的大概400G iOS学习视频及学习资料,都是干货哦!可以新浪微博私信?关注极客James,期待与您的共同学习和探讨!!由于时间有限,每天在

raywenderlich写的关于内存管理,第二篇,关于如何检查内存泄露

原文链接地址:http://www.raywenderlich.com/2696/how-to-debug-memory-leaks-with-xcode-and-instruments-tutorial 著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享.请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢! 教程截图: 作为一名无证程序员,无论你多么精通Objective-C的内存管理,随着时间的推移,你也不可避免的犯内存相关的错误.但通常因为代

Objective -C Memory Management 内存管理 第一部分

Objective -C Memory Management??内存管理??第一部分 Memory management is part of a more general problem in programming called resource management. 内存管理是资源管理的一部分. Every computer system has finite resources for your program to use. These include memory, open fi

Objective-C(内存管理)

引用计数器 每个OC对象都有一个占4个字节存储空间的引用计数器 当使用或创建一个对象时,新对象的引用计数器默认是1 retain:可以使引用计数器+1 release:可以是引用计数器-1 retainCount:获得当前的引用计数器的值 当对象被销毁时,会重写dealloc方法 -(void)dealloc { // 这句必须放在最后面 [super dealloc]; } 僵尸对象:所占内存已经被回收的对象,僵尸对象不能再使用 野指针:指向僵尸对象(不可用的内存)的指针 错误:EXC_BAD

IOS学习笔记3—Objective C—简单的内存管理

今天简述一下简单的内存管理,在IOS5.0以后Apple增加了ARC机制(Automatic Reference Counting),给开发人员带来了不少的方便,但是为了能更好的理解IOS内存管理机制,还是需要对其比较了解. 1.在OC中,每个对象都有一个保留计数,创建时每个对象都有一个初始值为1的保留计数,释放时,保留计数都为0 2.创建自动释放的对象 要求以一个方法创建对象时,以自动释放的形式返回该对象是一个很好的编程实践 +(Car *)car { Car *myCar = [[Car a

iOSDay17之内存管理

1.内存管理的方式 1> iOS应用程序出现Crash(闪退),90%的原因是因为内存问题. 2> 内存问题 野指针异常:访问没有所有权的内存,如果想要安全的访问,必须确保空间还在 内存泄露:空间使用完之后没有及时释放 过度释放:对同一块存储空间释放多次,立刻crash 内存溢出:所有存储空间被占用 3> 管理内存的三种方式 垃圾回收机制:程序员只需要开辟存储空间,系统会自动回收内存.java采用的该机制 MRC:手动引用计数机制,由开发人员开辟空间,手动添加影响引用计数增加或减少的方法

Objective-C----MRC内存管理 、 自动释放池 、 面向对象三大特性及封装 、 继承 、 组合与聚合

1 MRC练习 1.1 问题 引用计数是Objective-C语言采用的一种内存管理技术,当一个对象被创建在堆上后,该对象的引用计数就自动设置为1,如果在其它对象中的对象成员需要持有这个对象时,则该对象的引用计数被加上1,此时如果该对象被释放,内存管理程序将首先把该对象的引用计数减1,然后判断该对象的引用计数是否为0,由于其它对象在持有该对象时将引用计数加了1,所以此时该对象的引用计数减1后不为0,则内存管理程序将不会释放该对象.直到持有该对象的其它对象也被释放时,该对象的引用计数再次减1,变为