iOS内存管理

MRC

对象持有讨论

先看一个例子:

// 这个方法生成对象并返回
- (id)object
{
    // 通过 alloc/new/copy/mutableCopy 方法生成对象并自己持有
    id obj = [[NSObject alloc] init];
    
    /**
     autorelease使得自己不去持有这个对象
     自己持有对象的话,可以通过[obj release] 去释放对象,但当对象自身autorelease时,释放是由自身自动释放的,就不能通过release了(个人理解),这个时候去调用release会造成崩溃.
     */
    
    [obj autorelease];
    
    return obj;
}

// 方法调用:

/** 
 这里通过方法 object获取到对象,这个对象是存在的,但是自己并不持有这个对象,对自己不持有的对象是不能去释放他的.
 这个对象为什么存在,上面不是自己加了autorelease吗?
 其实这就是autorelease和release的区别:调用release会立即释放,但autorelease不会,调用autorelease会将对象注册到autoreleasepool中,当pool结束时会自动调用release.这里其实存在着一个哨兵指针. autoreleasepool 的释放是 NSRunloop 决定的.具体可以参考杨潇玉的博客.
 */
id obj1 = [obj0 object];

// 如果让obj1区持有这个对象呢? 可以调用retain,他可以将调用autorelease方法取得的对象变为自己持有
[obj1 retain];

总结:释放非自己持有的对象会造成程序崩溃,因此绝不能释放非自己持有的对象

关于内存释放的研究

NSAutoreleasePool什么时候释放?NSAutoreleasePool的生命周期是怎样的?是在方法体调用结束就释放?其实不是.

在大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象.典型例子是读入大量图像时,图像文件读入到NSData对象,从中生成UIImage对象.这种清空下会产生大量autorelease对象.这种情况就需要再适当的地方生成,持有,废弃NSAutoreleasePool对象.

比如在MRC时可以这样写:

for(int i = 0; i<100; i++) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // 读入图像,大量产生autorelease对象
    //  ........
    [pool drain];   // 这句代码可以让autorelease对象被一起release
}

Zone

我们经常看到不少方法里有这个单词,比如NSZoneMalloc,NSDefaultMallocZone等等.这个单词是干什么的呢?

NSZone其实是为了防止内存碎片化而引入的结构.它ui内存分配的区域本身进行多重化管理,根据对象目的,大小分配内存,提高内存管理效率.

但是根据苹果官方所说,现在的运行时系统,内存管理的效率本身已经很高,使用区域来管理内存反而会引起内存使用效率低下及源代码复杂化.

ARC

首先看看ARC中的4个修饰符:

  • __strong (默认修饰符)
  • __weak
  • __unsafe_unretained
  • __autoreleasing

在MRC中有个问题,通过alloc的方式得到的对象会被自己所持有,而通过比如NSArrayarray方法得到的对象不会被持有:

**  MRC  **
// obj持有对象
id obj = [[NSObject alloc] init];

// array不持有对象
NSArray *arr = [NSMuableArray array];

那在ARC中是怎么样的呢?

**  ARC  **
// obj持有对象
id obj = [[NSObject alloc] init];
// 相当于:
id __strong obj = [[NSObject alloc] init]; // 由于obj为强引用,所以自己持有对象

/**
 这里的array也是持有对象的.
 array类方法让arr取得 非自己生成并持有  的对象
 但因arr为强引用,所以持有对象
 在超出作用域(方法体)后,强引用失效,会自动释放自己所持有的对象
*/
NSArray *arr = [NSMuableArray array];
// 相当于
NSArray *__strong arr = [NSarray array];

赋值方法对对象的引用

__strong
id __strong objA = [[NSObject alloc] init];  // objA持有对象A的强引用
id __strong objB = [[NSObject alloc] init];  // objB持有对象B的强引用
id __strong objC = nil;  // objC不持有任何对象

/**
 objA持有由objB赋值对象B的强引用
 因为objA被赋值,所以原先持有的对象A的强引用失效
 对象A的所有者不存在,因此废弃对象A
 
 此时,持有对象B的强引用的变量为
 objA 和 objB
*/
objC = objA;

/**
 objC 持有由objA赋值的对象B的强引用
 此时,持有对象B的强引用的变量为
 objA 和 objB 和 objC
*/
objB = objC;

/**
 nil被赋予了objB,所以objB对对象B的强引用失效
 
 此时,持有对象B的强引用的变量为
 objA 和 objC
 
 
 这里做一个理解,为什么objB = nil, objA还是和objC还是指向对象B呢? objA和objC 会变成nil吗?
 不会.objB = nil;这句话的意思是objB抛弃了原先的对象,重新指向了一个对象(nil),原先的对象B会通过retainCount - 1 的方式回应这次'抛弃'. 但如果B不是通过从新指向另一块内存的方式,而是改变自身,比如 [objB addObject],那么他是对自身对象B进行的操作,这个时候objA和objC也会相应被改变.
*/
objB = nil;

总结:

  • 自己生成的对象,自己所持有
  • 非自己生成的对象,自己也可以持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放
__weak

__strong修饰符基本可以完美的进行内存管理了,但是遇到循环引用就需要__weak了.

比如两个Person实例 personApersonB,都有一个id类型的obj属性, obj属性被分别赋值 personBpersonA,即:

Person personA = [[Person alloc] init]; // A 对象
Person personB = [[Person alloc] init]; // B 对象

personA.obj = personB;
personB.obj = personA;

这个时候A对象的持有者是两个 personApersonBobj 属性;
B对象的持有者也是两个 personBpersonAobj 属性;

personA 变量超出其作用域,强引用失效,自动释放对象A, 当personB 变量超出其作用域,强引用失效,自动释放对象B. 此时,持有 A 对象的强引用变量为对象Bobj,持有B对象的强引用为对象A的obj. 发生内存泄漏.

所谓内存泄漏,就是应当废弃的对象在超出其生命周期后继续存在.

上面的例子,如果A 对象持有自身,即personA.obj = personA,这样也会造成循环引用.

还有一个例子,这里做一下分析:

id __weak objA = nil;
{
    id __strong objB = [[NSObject alloc] init];
    objA = objB;
    
    NSLog(@"A = %@",objA);
}

NSLog(@"A = %@",objA);

输出结果为:

A = <NSObject: 0x45f140e>
A = (null)

对于id __weak objA = nil;这句代码,我之前也一直不理解,先不论objA是否=nil, __weak修饰的对象如果没有强持有者不会立即释放掉吗,为什么objA 还存在?

其实,objA是指针,是在栈里的,objA指向的对象(比如对象A)是在堆内存里的,确实,对象A释放后objA也会随之销毁,但是由于objA是在栈里的,并且objA其实是autorelease的,该指针只会被标记为要释放,等待autorelease要释放(drain)时候,objA才会通过哨兵对象确定要被释放,从而发送release消息将它释放掉,所以在这个方法体里,objA还是存在的.所以,在使用附有__weak修饰符的变量时就必定要使用注册到autorelepool中的对象.其实,__weak申明的变量会自动将对象注册到autoreleasepool中.

所以,第一次打印,是打印的objA通过弱引用持有的对象,这个对象在方法体结束后被释放了,所以第二次打印为null.

<font size='3' color='0000ff'>weak自动置为nil的实现</font>

id __weak obj1 = obj;

这句话做了什么?

  1. 首先,obj1通过obj进行初始化,调用函数
objc_initWeak(&obj1, obj)

当释放的时候,第二个参数传递0,即

objc_destroyWeak(&obj1, 0)
  1. objc_storeWeak函数把obj1的地址和被赋值对象通过键值对的方式注册到weak表里.当对象释放的时候,通过废弃对象的地址作为键值进行检索,就能获取到对应__weak变量的地址,然后将所有附有__weak修饰的变量地址全部赋值nil,之后再把她从weak表中移除掉就好了.
  2. 大量使用__weak会消耗相应的CPU资源(要注册weak表,并且一个键值,可以注册多个变量的地址),所以一般只在需要避免循环引用的时候使用__weak.
__unsafe_unretained

这个修饰符是不安全的.ARC的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器内存管理的对象.

同样用上面的例子做说明,第一行换成id __unsafe_unretained objA = nil;

打印结果为:

A = <NSObject: 0x45f140e>
A = <NSObject: 0x45f140e>

奇怪,第二次打印结果正常,这是怎么回事?

当方法体出来后,objB强引用失效,自动释放自己持有的对象,这个时候没有强引用去持有这个对象,对象释放.所以objA变量表示的对象已经被废弃,变成悬垂指针,这是一个错误访问.所以这一次的打印只是碰巧正常运行而已.虽然访问了已经被废弃的对象,但是应用程序在个别运行状况下才会崩溃.

__autoreleasing

ARC下,autorelease是不能用的(这个修饰符是可以使用的),包括NSAutoreleasePool类也不能用,MRC下是这样使用的:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; 

drain方法废弃正在使用的NSautoreleasePool对象的过程为:

- (void)drain
{
    [self dealloc];
}

- (void)dealloc
{
    [self emptyPool];
    [array release];
}

- (void)emptyPool
{
    for (id obj in array){
        [obj release];
    }
}

ARC下,该源代码写成如下这样:

@autoreleasepool {
    
    id __autoreleasing obj = [[NSObject alloc] init]; // ARC无效时会调用autorelease方法.另外,__autoreleasing修饰符一般是可以非显示地使用的
    
}

在使用alloc/new/copy/mutableCopy以外的方法来取得对象的时候,该对象会被注册到autorelpool.编译器会检查方法名是否以这几个单词开始,如果不是的则自动将返回的对象注册到autoreleasepoll.

另外,init方法返回值的对象不会注册到autoreleasepool.

不管是否使用了ARC,调试用的非公开函数_objc_autoreleasePoolPrint()都是可以使用的.不过有可能报错Implicit declaration of function - C99,这种情况可以通过Build Setting -> C Language Dialect修改为GNU99GNU89解决.

使用ARC的时候,对象型变量补鞥呢作为C语言结构体(struct/union)的成员.因为ARC把内存管理的工作交给编译器,那么编译器必须知道并管理对象的生存周期.要把对象型变量加入到结构体成员中时,可以强制转换为void *或者附加__unsafe_unretained修饰符(因为__unsafe_unretained修饰符修饰的变量不属于编译器的内存对象).

**  MRC  **
id obj = [[NSObject alloc] init];
void *p = obj;

// 也可以反过来赋值
id obj2 = p;
[obj2 release];
**  ARC  **
// 要使用__bridge桥接转换
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id obj2 = (__bridge id)p;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 一.内存管理 /引用计数 Objective-C 中的内存管理,也就是引用计数 1.1内存管理的思考方式 自己生成...
    sellse阅读 310评论 0 0
  • 终于明白那些年知其然而不知其所以然的iOS内存管理方式 前言 从我开始学习iOS的时候,身边的朋友、网上的博客都告...
    枫宇翔阅读 7,327评论 8 49
  • 貌似每个iOS开发者都有一篇属于自己的内存管理,记录了自己对内存管理理解的深度以及广度,所以我也来记录一下我的理解...
    Bugfix阅读 2,251评论 0 3
  • # 前言 反复地复习iOS基础知识和原理,打磨知识体系是非常重要的,本篇就是重新温习iOS的内存管理。 内存管理是...
    Vein_阅读 780评论 0 2
  • 你有没有觉得,当你问为什么的时候, 你的思维是停留在过去的。 你有没有想过,当你质问别人的时候, 你仍然是纠结在过...
    人间清醒叔阅读 569评论 0 2