• Objective-C 内存管理


    本文记录Objective-C在内存管理方面的一些注意点。另有一篇转载的未公开笔记——Objective-C内存管理机制学习笔记【转】

    ARC 与 MRC

    iOS 有两种内存管理机制:ARC 与 MRC。

    ARC:Automatic Reference Counting,自动引用计数

    MRC:Manual Reference Counting,手动引用计数

    ARC 的原理:编译器在编译期间在代码里自动插入 retain/release

    ARC 禁止手动调用 retain/release/retainCount/dealloc

    引用计数

    在引用计数中,每一个对象负责维护对象所有引用的计数值。

    创建对象后,该对象的引用计数值为1。一旦引用计数的值为0,则对象就会自动dealloc。

    包含alloc/new/copy/mutableCopy的方法 引用计数+1

    retain 引用计数+1

    release 引用计数-1

    对于每个 retain,一定要对应一个 release 或一个 autorelease。

    查看方法:[object retainCount];

    生成并持有对象 alloc/new/copy/mutableCopy等方法 +1
    持有对象 retain方法 +1
    释放对象 release方法 -1
    废弃对象 deallco方法

    内存管理的思考方式

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

    对 “自己” 一词的理解,可以解释” 对象的使用环境 “,或者理解为程序猿 “本身”

    // 1. 自己生成的对象,自己持有
    // 自己生成并持有对象,此时对象的引用计数为 1
    id obj = [NSObject alloc] init];
    
    // 2. 非自己生成的对象,自己也能持有
    // 取得非自己生成并持有的对象,此时 obj 是经过 autorelease 的
    id obj = [NSMutableArray array];
    // 然后持有
    [obj retain];
    
    // 3. 自己持有的对象需要自己释放
    // 自己生成并持有对象
    id obj = [NSObject alloc] init];
    // 自己释放
    [obj release];
    
    // 4. 非自己持有的对象无法释放
    // 自己生成并持有对象,此时对象的引用计数为 1
    id obj = [NSObject alloc] init];
    // 释放,此时引用计数为 0
    [obj release];
    // 再次释放,会引发 Crash
    [obj release];

    添加对象到Array时记得release

    1. obj对象创建后, 计数为1
    2. obj对象加入到array中后,计数+1,为2
    3. obj对象从array中移除后,计数-1,为1

    Object *obj = [[Object alloc] init];   //1
    [array addObject:obj];                 //2
    [array removeObjectAtIndex:0];         //1
    /*此时obj的引用计数为1,内存泄漏*/

    addObject和removeObjectAtIndex是一对,由系统管理引用计数。而我们输入的Object *obj = [[Object alloc] init];并没有一个release与之对应,所以造成obj没有被正确释放。

    解决方法是在obj对象添加到array后,release它。

    Object *obj = [[Object alloc] init];   //1
    [array addObject:obj];                 //2
    [obj release];               //1
    [array removeObjectAtIndex:0];         //0
    /*此时obj的引用计数为0,内存不泄漏*/

    NSArray

    NSArray* immutableArray = [[NSArray alloc] initWithArray:mutableArray]
    NSArray* immutableArray = [NSArray arrayWithArray:mutableArray]; 
    NSArray* immutableArray = [mutableArray copy];

    1. alloc和copy都会分配内存,需要手动release。所以调用第一个和第三个都需要 [immutableArray release].

    2. arrayWithArray也会分配内存,不过系统会来管理这块内存,不需要手动release。如果想要自己管理,可以这样:

    NSArray* immutableArray = [[NSArray arrayWithArray:mutableArray] retain];
    
    [immutableArray release];

    系统自带的绝大多数类方法返回的对象,都是经过 autorelease 的,比如 [NSArray array]、[NSNumber numberWithInt]

    dealloc负责本类属性的释放及调用父类的dealloc

     当类中包含其他指针,就要在dealloc函数中手动一一release它们,最后记得[super dealloc]。

    让函数返回一个autorelease对象

    - (NSString *)f
    {
        NSString *result = [[NSString alloc] initWithFormat:@"Hello"];
        return result;
    }

    这样做其实是会内存泄漏的。alloc方法会创建出来一个string对象,它的retain计数为1。因此该string对象返回时,retain计数为1。在其他对象调用f方法得到string对象后,它一般会retain该string对象(因为调用者认为f返回的应该是一个autorelease对象)。这时,string对象的retain计数变成2。然后调用者在不再需要stirng对象时,他将会调用release(因为他retain了一次,所以会release一次)。这时string对象的retain计数变成1。正如你所想, string对象没有得到释放。

    错误的解决方法:让函数返回前使用[result release];

    这样返回的函数对象其实已经是空的了。不可行。

    正确的解决方法:让函数返回一个autorelease对象。

    - (NSString *)f
    {
        NSString *result = [[NSString alloc] initWithFormat:@"Hello"];
        return [result autorelease];
    }

    Autorelease

    Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。 
    autorelease和release没什么区别,只是引用计数减一的时机不同而已,autorelease会在对象的使用真正结束的时候才做引用计数减一。 

    函数返回的是一个autorelease对象,而接到它的对象一般需要retain,然后有retain就需要我们手动release。

    如果有AutoreleasePool,那么在内存池管理的范围内的autorelease都不需要我们手动释放。

    • NSString *str1 = @"constant string";常量字符串已经自动加入autorelease pool,不需要自己管理内存了。
    • [NSString stringWithString: @"String"] 这种 method 使用了 autorelease pool,不需要自己管理内存了。
    • alloc method 如 [[NSString alloc] initWithString: @"String"] 则沒有使用 auto release pool,需要自己release。

    自动释放池对象通常以如下模式创建:

    [[[Object alloc] init] autorelease];
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
    A *a = [[A alloc] init];    //引用计数为1
    [pool drain];                //引用计数依然为1
    [a retain];                    //引用计数为2
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
    [a autorelease];            //将a添加到pool中,当pool释放的时候,a也被释放
    [pool drain];                //引用计数为1
    [a release];                //引用计数为0
    • 消息autorelease的作用是将对象添加到自动释放池中,当池被释放的时候,系统将向池中的对象发送一条release的消息。
    • 消息autorelease并不会影响对象的引用计数。

    autorelease可能导致存在大量临时对象

    - (void)f
    {
      for(int i = 0; i < 100000; ++i)
      {
        //getData返回一个autorelease对象
        NSData *data = [self getData];
      }
      //在这里100000个数据对象都还有效
    }

    所以,autorelease可能导致存在大量临时对象。

    解决方法1:在循环中释放对象

    - (void)f
    {
      for(int i = 0; i < 100000; ++i)
      {
        NSData
    *data = [[NSData alloc]init]; /* * set data, use data */
        [data release];   }   //在这里100000个数据对象都被成功释放 }

    解决方法2:循环内部创建一个自动释放池

    - (void)f
    {
      for(int i = 0; i < 100000; ++i)
      {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
    
        //getData返回一个autorelease对象
        NSData *data = [self getData];
    
        [pool drain];
      }
      //在这里100000个数据对象都被成功释放
    }

    setter中的内存管理

    - (void)setName:(NSString *)newName
    {
        name = newName;
    }

    这样写有什么不对的地方呢,当newName在某个地方被release后,该name将失效!
    改进后的写法应该如下,以防其他人释放name引用的对象而导致name失效。

    - (void)setName:(NSString *)newName
    {
        name = newName;
        [name retain];
    }

    这样写也有问题,当第二次调用setName的时候,原来的name占的空间并没有释放;而且retain之后什么时候release这个对象?
    改进后的写法应该如下

    - (void)setName:(NSString *)newName
    {
        [name release];    //释放旧值
        name = [newName retain];
    }
    
    - (void)dealloc
    {
        [name release];    //对应setName中的retain
        [super dealloc];
    }

    可是可是可是,这样还是有问题,假如自己传值给自己的时候会怎样呢?所以,最正确的应该是

    - (void)setName:(NSString *)newName
    {
        [newName retain];  //注意,顺序一定是先retain再release。
        [name release];
        name = newName;
    }
    
    - (void)dealloc
    {
        [name release];    //对应setName中的retain
        [super dealloc];
    }

    注意,顺序一定是先retain再release。当然还有其他写法,详见《Cocoa® Programming for Mac® OS X》中的内存管理章节,不过个人比较推崇这种写法。

    最后的问题是,当newName改变的时候,name也会跟着改变,因为这是浅复制。如果想要让二者独立的话,即深复制,应该这样写

    - (void)setName:(NSString *)newName
    {
        if (name != newName)  //防止复制自身
        {
            [name release];
            name = [[NSString alloc] initWithString:newName];
        }
    }
    - (void)dealloc
    {
        [name release];
        [super dealloc];
    }

    其他

    1.

    NSNumber *myInt = [NSNumber numberWithInteger:100];    //引用计数为1

    2.

    myInt = [myArr objectAtIndex:0];
    [myArr removeObjectAtIndex:0];

    此时,myInt引用的对象失效。应当修改为:

    myInt = [myArr objectAtIndex:0];
    [myInt retain];
    [myArr removeObjectAtIndex:0];

    3.

    NSString *s1 = @"s1";    //引用计数为0xffffffff(很多f就对了)
    NSString *s2 = [NSString stringWithString:@"s2"];    //引用计数为0xffffffff
    NSMutableString *s3 = [NSMutableString stringWithString:@"s3"];    //引用计数为1

    为什么呢,因为s1是常量字符串,s2是使用了常量字符串初始化的不可变字符串对象,都没有引用计数机制。

    参考文献

    Objective-C Beginner's Guide

    Cocoa® Programming for Mac® OS X》中的内存管理章节

    Objective-C高级编程》中的自动引用计数部分

    objc内存管理

  • 相关阅读:
    在数据库中 存储图片 以及 在界面中显示图片(存储图片路径)- 这种方法相对与存储二进制文件好
    # 会员注册与登录模块
    文本文件从磁盘读取、写入
    简单的web三层架构系统【第五版】
    Nginx负载均衡中后端节点服务器健康检查的一种简单方式
    编译安装php-7.1.17及部分扩展
    wkhtmltopdf 安装过程不包含php扩展部分
    Centos6下安装中文字体
    xen 配置vm 跟随xen server一起启动
    CENTOS 升级Nodejs 到最新版本
  • 原文地址:https://www.cnblogs.com/chenyg32/p/3859110.html
Copyright © 2020-2023  润新知