iOS与OS X内存管理

  • 本文的写作目的是为学习记录,同时分享给大家,希望大神能够对文中错误的理解进行指正。
  • 如果文章内容涉及到其他已经发表了,但文章中又未提及转载事项,请及时与本人联系。
  • 本文为个人理解,如果部分知识点与真实情况有出入,请忽略本文。

1 相关知识点

1.1 内存管理

软件运行的时候,对计算机内存资源的分配和使用进行管理的策略。

1.2 引用计数

一种内存管理策略。(以下解释来自百度百科)

  • 当一个新的引用指向对象时,引用计数器就递增;
  • 当去掉一个引用时,引用计数就递减;
  • 当引用计数到零时,该对象就将释放占有的资源。

1.3 iOS与OS X的自动释放池

把所有生成的对象放到一个“池子”里,当超过“池子”的作用域,就会自动帮你释放对象,不用你手动release。

1.4 iOS与OS X的自动引用计数

  • “俗称”ARC(Automatic Reference Counting)。
  • 如名,就是iOS和OS X系统内自行处理内存管理,不用你专门敲代码去给对象的引用计数+1、-1。
  • 从iOS 5和OS X Lion开始引入了自动引用计数,所以除非你需要适配在那之前的版本,不然你完全可以打开ARC(想知道怎么开?请自行百度)。

1.5 内存泄漏

应该让出的内存资源,却一直占着不放。

2 引用计数

  • 生成对象(+1);
  • 持有对象(+1);
  • 释放对象(-1);
  • 废弃对象(=0)。

3 iOS与OS X的引用计数

3.1 生成对象(alloc/new/copy/mutableCopy)

  • alloc方法
    生成对象,并用某个变量去持有对象;
  • new方法
    也是生成对象,并用某个变量去持有对象([NSObject new]与[[NSObject alloc] init]完全一致);
  • copy方法
    生成对象,并用某个变量去持有对象的副本;
  • mutableCopy方法
    同copy,区别在于生成的对象,copy生成不可变更的对象,mutableCopy则生成可变。

使用以上方法名称开头,并且以驼峰拼写法命名的方法,也意味着自己生成并持有对象。例如:

allocFirstObject
newSecondObject
copyThirdThat
mutableCopyFourthObject等。

3.2 持有对象(retain)

除去上述的四种方法之外取得的对象,可以用retain方法持有该对象。例如:

id testArr = [NSMutableArray array];
[testArr retain];

3.3 释放对象(release)

自己持有的对象,一旦不再需要,持有者有义务释放该对象。释放使用release方法。例如:

id testObj = [[NSObject alloc] init];
[testObj release];

3.4 废弃对象(dealloc)

当引用计数为0时,则调用dealloc方法废弃对象。
如果需要显示调用dealloc方法,即在废弃对象后进行必要的操作,则需要调用[super dealloc]。例如:

- ( void ) dealloc
{
    [super dealloc];
}

3.5 释放之后再次释放

在持有对象之后,需要释放对象,但不能再次释放已经释放的对象。下例会Crash。例如:

id testObj = [[NSObject alloc] init];
[testObj release];
[testObj release];

3.6 释放自己不持有的对象

非自己生成的对象,需要用retain持有,如果没有用retain持有,就释放也会Crash。例如:

id testObj = [NSMutableArray array];
[testObj release];

4 iOS与OS X的自动释放池autorelease

4.1 具体过程:

  • 生成自动释放池;
  • 调用autorelease方法,将对象放进自动释放池中;
  • 废弃自动释放池,所有在池中的对象自动调用release方法;

例如:

@autoreleasepool {
  Id testObj = [[NSObject alloc] init];
  [testObj autorelease];
}
//也可以通过生成NSAutoreleasePool对象的方式,来生成自动释放池
//通过生成NSAutoreleasePool对象的方式,需要手动废弃自动释放池(使用drain方法)。

4.2 注意事项

  • 当大量读取大容量文件时,要在适当的地方生成、持有或废弃自动释放池。如果不及时释放自动释放池所占用的内存,会导致内存占用过大而Crash。
  • Cocoa框架中,部分方法返回已调用autorelease方法的对象。具体信息请查阅官方文档。

5 iOS与OS X的自动引用计数(ARC)

所有ARC的操作需要在Xcode4.2之后进行,并且打开ARC功能(Xcode4.2之后默认打开)。

5.1 变量修饰符

5.1.1 __strong

  • “强引用”。
  • 生成并持有对象,非生成也可以通过赋值持有对象。
  • 超出作用域时,自动释放。
  • id类型和对象类型默认添加该修饰符。

例如:

{
    id __strong testObj = [[NSObject alloc] init];
    //;在ARC中,以上写法与id testObj = [[NSObject alloc] init]完全相同
}
//超出范围,自动释放

5.1.2 __weak

  • “弱引用”。
  • 弱引用不能持有对象。
  • 不能直接生成,需要间接赋值。
  • 弱变量持有对象时,若对象被废弃,则此变量自动失效或为nil。
  • 防止循环引用,而导致内存泄漏。

例如:

@interface Test : NSObject
{
    id __weak testObj;
}
- ( void ) setObject: (id __strong)obj;
@end

@implementation Test
- ( id )init
{
    self = [super init];
}
- ( void ) setObject: (id __strong)obj
{
    testObj = obj;
}

以下为引用:

{
    id test0 = [[Test alloc] init];
    id test1 = [[Test alloc] init];
    [test0 setObject:test1]; 
    [test1 setObject:test0];
    //由于testObj为__weak类型,不持有对象,此处不会发生循环引用
}
//如果testObj为__strong类型,则持有对象
//导致testObj一直持有对象,导致对象无法释放==内存泄漏

5.1.3 __unsafe_unretained

  • 该修饰符修饰的变量,不属于自动引用计数的范围。
  • 不能直接生成,需要间接赋值。
  • 如果赋值的对象被废弃或不存在,则程序有可能(有可能,即个别情况)崩溃。

例如:

id __unsafe_unretained testObj1 = nil;
{
    id testObj0 = [[NSObject alloc] init];
    testObj1 = testObj0;
    NSLog(@”%@”,testObj1);
}
NSLog(@”%@”,testObj1);
//其中testObj0的对象引用已被废弃,则访问testObj1有可能出现程序崩溃

5.1.4 __autoreleasing

  • 如名,ARC的自动释放池(以下简称“池”)。
  • 在iOS的main函数中就已经生成了池,并且包含程序的所有内容。
  • 不能用生成NSAutoreleasePool的方式,来生成自动释放池,编译器会报错。
  • 正常情况下,如果你用@autoreleasepool生成池,必须显式将对象注册到池中。

但也存在个别情况:
1、非自己生成并持有的对象,会自动注册到池中。
例如:

@autoreleasepool {
    id testObj = [NSMutableArray array];
    //生成的对象已自动注册到池中
}

2、访问附有__weak修饰符的变量时,会将即将引用的对象注册到池中。
例如:

@autoreleasepool {
    id testObj0 = [[NSArray alloc] init]; 
    //此处若需将testObj0注册到池中,须显式使用__autoreleasepool修饰符声明

    id __weak testObj1 = testObj0;
    //在赋值的同时,将testObj0引用的对象注册到池中
}

3、id的指针、对象的指针变量会自动附上__autoreleasepool修饰符。
例如:

@autoreleasepool {
    id testObj0 = [[NSObject alloc] init]; // testObj0自动附上__strong修饰符
    id *testObj1 = &testObj0; //*testObj1自动附上__autoreleasepool修饰符
    //此处会报错,因为指针赋值时,修饰符必须一致
}

Tips:无论ARC是否开启,_objc_autoreleasePoolPrint();函数都可以打印出已经注册到当前池中所有对象。

5.2 iOS与OS X中ARC开启的状态下,注意事项

5.2.1 禁用关键字

  • 不能使用alloc/retain/release/retainCount/autorelease。
  • 估计大家都知道这点,在ARC开启状态下,以上这些方法都是不能使用的。

5.2.2 须遵守的命名规则

  • 在ARC未开启时候的命名规则,参考3.1 最后一条所述。
  • 而在ARC开启的时候,除去以上规则同样有效外,还要考虑init关键字:
    1、以init开头(并且用驼峰拼写发)命名的方法,必须是一个实例方法,并且需要返回对象。
    2、以init开头(并且用驼峰拼写发)命名的方法,返回的对象并不自动注册到自动释放池中。

5.2.3 不要显示调用dealloc

  • 对象被废弃时,不管ARC是否开启,都会调用对象的dealloc方法。
  • 参考以上所述,可以在类中实现dealloc方法(实现dealloc方法在ARC是否开启之间的区别,请参考3.4)。

例如:

- ( void ) dealloc
{
    //无需[super dealloc]
}
  • 如上例中所述,在ARC开启的状态下,不要显式调用dealloc。

5.2.4 不要使用NSAutoreleasePool

在ARC开启的状态下,使用NSAutoreleasePool类生成自动释放池,编译器会报错。

5.2.5 不能使用区域(NSZone)

无论ARC是否开启,最好都不要用(其实我还没去了解这个“区域”是个什么东西o(╯□╰)o)。

5.2.6 对象型变量不能成为struct的成员

例如:

struct TestStruct {
    NSArray * array;
    //报错,不能用对象型变量
}

5.2.7 __bridge转换

  • 不能显示转换id和void *(C/C++语言中的空指针类型),需要用__bridge关键字进行转换。

例如:

id testObj = [[NSObject alloc] init];
void *testVoid = (__bridge void *)testObj;
id testObj1 = (__bridge id)testVoid;
  • __bridge_retained转换,可使要转换赋值的变量,也持有所赋值的对象。
  • __bridge_transfer转换,被转换的变量所持有的对象,在转换完成后释放。

Tips:Foundation框架与Core Foundation框架的对象互相转换,需要用__bridge关键字进行转换。

5.3 iOS与OS X的属性(property)

属性声明的属性与所有权修饰符的对应关系:

属性声明的属性 所有权修饰符
assign __unsafe_unretained修饰符
copy __strong修饰符(但是赋值的是被复制的对象)
retain __strong修饰符
strong __strong修饰符
unsafe_unretained __unsafe_unretained修饰符
weak __weak修饰符

5.4 容器

5.4.1静态容器(NSArray、NSDictionary、NSSet)

  • 除去__unsafe_unretained修饰符以外,__strong、__weak、__autoreleasing修饰符都能保证其指定变量初始化为nil。
  • 数组超出其作用域,则变量失效、对象释放(蛮押韵的)。

5.4.2 动态容器(NSMutableArray、NSMutableDictionary、NSMutableSet)

  • Objective-C的动态容器使用的基本规则,基本同静态一致。
  • iOS中C语言的动态容器:

1、声明动态容器,需要用指针。
例如:

id __strong *array = nil;

2、附有__strong修饰符的id型变量初始化为nil,附有__strong修饰符的id型指针变量初始化不一定为nil。
3、calloc函数,给附有__strong修饰符的id型指针变量分配内存。
例如:

array = (id __strong *)calloc(entries, sizeof(id));

4、操作__strong修饰符的变量,需要自己释放所有元素。释放所有元素后,利用free(array)的方法废弃内存块。
例如:

for (NSUInteger i = 0; i < entries; ++i) {
    array[i] = nil;
}
free(array);

5、以下函数会造成内存泄漏的危险,请慎用:

  • 用malloc函数分配内存(之后,可用memset函数将内存置零);
  • 用memcpy函数拷贝数组元素;
  • 用realloc函数重新分配内存块。

6、__weak修饰符与__strong修饰符使用方式相同;但__autoreleasing修饰符存在不可控因素,慎用;__unsafe__unretained修饰符在编译器的内存管理对象之外,只能作为指针类型使用。

参考:

  • 《Objective-C高级编程 iOS与OS X多线程和内存管理》【日】Kazuki Sakamoto Tomohiko Furumoto 著 黎华 译
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容