Objective-C 内存管理

news/2024/7/20 14:04:19 标签: 内存管理, 移动开发

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

引用计数

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

对象的初始引用计数值为1。如果引用计数的值为0,则对象就会自动dealloc。

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

retain 引用计数+1

release 引用计数-1

在 Objective-C 有2种管理内存的方法, 1) retain and release or 2) retain and release/autorelease。

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

查看方法:[object retainCount];

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

 

 

 

 

个人理解,所谓持有对象,可以简单理解为负责该对象的释放。

 

内存管理的思考方式

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

 

添加对象到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];

 

 

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内存管理

转载于:https://www.cnblogs.com/chenyg32/p/3859110.html


http://www.niftyadmin.cn/n/516946.html

相关文章

ExtJS中实现嵌套表格

先看效果&#xff1a; 代码如下&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><meta http-equiv"Content-Type"…

Endnote中IGRS Letters参考文献格式修改以及将其转为txt纯文本

该期刊endnote参考文献格式见链接&#xff0c;粉丝可下载。 该格式为自己编写&#xff0c;期刊名称没有缩写&#xff0c;因为编辑会帮助修改。此外&#xff0c;如果在endnote中查看某篇参考文献是conference proceedings&#xff0c;可以将其选择为conference paper. 由于IGRS…

android中sharedPreferences的用法

SharedPreferences介绍&#xff1a;做软件开发应该都知道&#xff0c;很多软件会有配置文件&#xff0c;里面存放这程序运行当中的各个属性值&#xff0c;由于其配置信息并不多&#xff0c;如果采用数据库来存放并不划算&#xff0c;因为数据库连接跟操作等耗时大大影响了程序的…

Origin常用操作||一点点更新

1. 绘图数值显示格式 *2*或者*2 " " 表示取小数点后两位数。源数据单元格式设置为显示两位&#xff0c;绘图时仍然为源数据原始小数点位数&#xff0c;因此需要再次设置。

Word排版——插入新公式||公式由斜式变横式

SCI论文插入公式时&#xff0c;有的期刊要求短公式/为横式。 Word中插入新公式除号/自动变成上下式&#xff0c; 此时不美观&#xff0c; 需要将其选中右击变为“斜式”&#xff0c; 再次选中右击&#xff0c;变为“横式”。 上面公式我丢了个括号&#xff0c;大家记得加上…

cocod2d-x 之 CCTMXTiledMap CCTMXLayer

cocos2dx框架自带的地图CCTMXTiledMap&#xff0c;继承自CCNode。CCTMXTiledMap的坐标系的原点位于左上角&#xff0c;以一个瓦片为单位&#xff0c;换句话说&#xff0c;左上角第一块瓦片的坐标为(0,0)&#xff0c;而紧挨着它的右边的瓦片坐标就是(1,0)。TileMap中的每一个瓦片…

一种叶片叶绿素含量(Leaf chlorophyll content, LCC)测定方法

pieces of the leaf were immediately cut using a hole punch with a diameter of 6 mm. 在叶片表面切割一个直径6毫米孔大小的部分。The pieces were ground in a clear mortar with a pestle, and then the leaf pigment mixture was placed in a 25‐ml volumetric flask w…

MFC编译Freetype2.3.7

从http://www.freetype.org下载源代码。 FreeType2库源码包中包含多种环境与编译器下的make文件&#xff0c;其中还包含vc的项目文件。 我用的是VC&#xff0c;所以首先找到VC环境的项目文件。该文件在 /builder/win32/visualc目录下面&#xff0c;该目录包含vc6与vs2005的项目…