iOS、内存管理

内存管理和分配

内存分为5个区域,分别指的是----->栈区/堆区/BSS段/数据段/代码段

栈:存储局部变量,当其作用域执行完毕之后,就会被系统立即收回

堆:存储OC对象,手动申请的字节空间,需要调用free来释放

BSS段:未初始化的全局变量和静态变量,一旦初始化就会从BSS段中回收掉,转存到数据段中

数据段:存储已经初始化的全局变量和静态变量,以及常量数据,直到结束程序时才会被立即收回

代码段:代码,直到结束程序时才会被立即收回

除了堆区,其他区域的中存储的数据,都是由系统自动释放的,堆区中的OC对象,是不会自动释放的,如果不主动释放,那么将在程序结束的时候才去释放

举例:

当第一个人进入办公室时,他需要使用灯,于是开灯,引用计数为1

当另一个人进入办公室时,他也需要灯,引用计数为2;每当多一个人进入办公室时,引用计数加1

当有一个人离开办公室时,引用计数减1,当引用计数为0时,也就是最后一个人离开办公室时,他不再需要使用灯,关灯离开办公室。

当你alloc一个对象objc,此时RC=1;在某个地方你又retain这个对象objc,此时RC加1,也就是RC=2;由于调用alloc/retain一次,对应需要调用release一次来释放对象objc,所以你需要release对象objc两次,此时RC=0;而当RC=0时,系统会自动调用dealloc方法释放对象。


Autorelease Pool  自动释放池

在释放池中的调用了autorelease方法的对象都会被压在该池的顶部(以栈的形式管理对象)。当自动释放池被销毁的时候,在该池中的对象会自动调用release方法来释放资源,销毁对象。以此来达到自动管理内存的目的。

在开发中,我们常常都会使用到局部变量,局部变量一个特点就是当它超过作用域时,就会自动释放。而autorelease pool跟局部变量类似,当执行代码超过autorelease pool块时,所有放在autorelease pool的对象都会自动调用release。它的工作原理如下:

 RunLoop : 1(等待消息) -> 2(将要处理消息) -> 3(处理消息) -> 4(消息处理完成) -> 1(等待消息)

你也可以将消息(message)这个词换成信号(signal)或者事件(Event)。当没有消息到来的时候,这个线程就会休眠,等待消息到来后触发处理过程。

AutoreleasePool是在什么时候创建的,又是在什么时候被销毁?

App启动后,系统在主线程RunLoop 里注册两个Observser,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。

第一个 Observer 监视的事件 

是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件

_BeforeWaiting(准备进入休眠) 时_

调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;

_Exit(即将退出Loop) 时_

调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

现在我们知道了AutoreleasePool是在RunLoop即将进入RunLoop和准备进入休眠这两种状态的时候被创建和销毁的。

所以AutoreleasePool的释放有如下两种情况。

一是Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。

二是手动调用AutoreleasePool的释放方法(drain方法)来销毁AutoreleasePool


创建一个NSAutoreleasePool对象

在autorelease pool块的对象调用autorelease方法

释放NSAutoreleasePool对象

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

// put object into pool

id obj = [[[NSObject  alloc]  init]  autorelease];

[pool drain];

/* 超过autorelease pool作用域范围时,obj会自动调用release方法 */

由于放在autorelease pool的对象并不会马上释放,如果有大量图片数据放在这里的话,将会导致内存不足。(一次runloop结束后释放)

for(int i = 0; i < numberOfImages; i++)

{

      /*   处理图片,例如加载

       *   太多autoreleased objects存在

       *   由于NSAutoreleasePool对象没有被释放

       *   在某个时刻,会导致内存不足 

       */

}


在ARC内存管理机制中,id和其他对象类型变量必须是以下四个ownership qualifiers其中一个来修饰:

__strong(默认,如果不指定其他,编译器就默认加入)

__weak

__unsafe_unretained

__autoreleasing


__strong ownership qualifier

如果我想创建一个字符串,使用完之后将它释放调用,使用MRC管理内存的写法应该是这样:

{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];   //@"Hello, world"对象的RC=1

    NSLog(@"%@", text);

    [text release];                      //@"Hello, world"对象的RC=0

}

而如果是使用ARC方式的话,就text对象无需调用release方法,而是当text变量超过作用域时,编译器来自动加入[text release]方法来释放内存

{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"对象 RC=1

    NSLog(@"%@", text);

}

/*  当text超过作用域时,@"Hello, world"对象会自动释放,RC=0   */

而当你将text赋值给其他变量anotherText时,MRC需要retain一下来持有所有权,当text和anotherText使用完之后,各个调用release方法来释放。{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"对象 RC=1

    NSLog(@"%@", text);

    NSString *anotherText = text;        //@"Hello, world"对象的RC=1

    [anotherText retain];                //@"Hello, world"对象的RC=2

    NSLog(@"%@", anotherText);

    [text release];                      //@"Hello, world"对象的RC=1

    [anotherText release];               //@"Hello, world"对象的RC=0

}

而使用ARC的话,并不需要调用retain和release方法来持有跟释放对象。{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];   //@"Hello, world"对象的RC=1

    NSLog(@"%@", text);


    NSString *anotherText = text;        //@"Hello, world"对象的RC=2

    NSLog(@"%@", anotherText);

}

/ *  当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法   

@"Hello, world"对象的RC=0   */

除了当__strong变量超过作用域时,编译器会自动加入release语句来释放内存,如果你将__strong变量重新赋给它其他值,那么编译器也会自动加入release语句来释放变量指向之前的对象。例如:

{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"对象的RC=1

    NSString *anotherText = text;        //@"Hello, world"对象的RC=2

    NSString *anotherText = [[NSString alloc] initWithFormat:@"Sam Lau"];  // 由于anotherText对象引用另一个对象@"Sam Lau",那么就会自动调用[anotherText release]方法,使得@"Hello, world"对象的RC=1, @"Sam Lau"对象的RC=1

}

/*

 *  当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法,

 *  @"Hello, world"对象的RC=0和@"Sam Lau"对象的RC=0

 */


我们总结一下编译器是按以下方法来实现的:

对于规则1和规则2,是通过__strong变量来实现,

对于规则3来说,当变量超过它的作用域或被赋值或成员变量被丢弃时就能实现

对于规则4,当RC=0时,系统就会自动调用


__weak ownership qualifier

其实编译器根据__strong修饰符来管理对象内存。但是__strong并不能解决引用循环(Reference Cycle)问题:对象A持有对象B,反过来,对象B持有对象A;这样会导内存造成内存泄露问题。



@interface Test : NSObject

@property (strong, nonatomic) id objc;

@end

{

    Test *test1 = [Test new];        /* 对象a */

    /* test1有一个强引用到对象a */


    Test *test2 = [Test new];        /* 对象b */

    /* test2有一个强引用到对象b */


    test1.objc = test2;              /* 对象a的成员变量objc有一个强引用到对象b */

    test2.objc = test1;              /* 对象b的成员变量objc有一个强引用到对象a */

}

/*   当变量test1超过它作用域时,它指向a对象会自动release

 *   当变量test2超过它作用域时,它指向b对象会自动release

 *   此时,b对象的objc成员变量仍持有一个强引用到对象a

 *   此时,a对象的objc成员变量仍持有一个强引用到对象b

 *   于是发生内存泄露

 */

如何解决?于是我们引用一个__weakownership qualifier,被它修饰的变量都不持有对象的所有权,而且当变量指向的对象的RC为0时,变量设置为nil。例如:

__weak NSString *text = [[NSString alloc] initWithFormat:@"Sam Lau"];

NSLog(@"%@", text);

由于text变量被__weak修饰,text并不持有@"Sam Lau"对象的所有权,@"Sam Lau"对象一创建就马上被释放,并且编译器给出警告,所以打印结果为(null)。

所以,针对刚才的引用循环问题,只需要将Test类的属性objc设置weak修饰符,那么就能解决。

@property (weak, nonatomic) id objc;


__unsafe_unretained ownership qualifier

__unsafe_unretained ownership qualifier,正如名字所示,它是不安全的。它跟__weak相似,被它修饰的变量都不持有对象的所有权,但当变量指向的对象的RC为0时,变量并不设置为nil,而是继续保存对象的地址;这样的话,对象有可能已经释放,但继续访问,就会造成非法访问(Invalid Access)。例子如下:

__unsafe_unretained id obj0 = nil;

{

    id obj1 = [[NSObject alloc] init];     // 对象A

    /* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */

    obj0 = obj1;

    /* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */

    NSLog(@"A: %@", obj0);

}

/* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */

NSLog(@"B: %@", obj0);

/* 由于obj0是__unsafe_unretained,当它指向的对象RC=0时,它会继续保存对象的地址,所以两个地址相同 */


如果将__unsafe_unretained改为weak的话,两个打印结果将不同

__weak id obj0 = nil;

{

    id obj1 = [[NSObject alloc] init];     // 对象A

    /* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */

    obj0 = obj1;

    /* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */

    NSLog(@"A: %@", obj0);

}

/* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */

NSLog(@"B: %@", obj0);

/* 由于obj0是__weak, 当它指向的对象RC=0时,它会自动设置为nil,所以两个打印结果将不同*/


__autoreleasing ownership qualifier

引入ARC之后,让我们看看autorelease pool有哪些变化。没有ARC之前的写法如下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

// put object into pool

id obj = [[NSObject alloc] init];

[obj autorelease];

[pool drain];

/* 超过autorelease pool作用域范围时,obj会自动调用release方法 */

引入ARC之后,写法比之前更加简洁:

@autoreleasepool {

    id __autoreleasing obj = [[NSObject alloc] init];

}相比之前的创建、使用和释放NSAutoreleasePool对象,现在你只需要将代码放在@autoreleasepool块即可。你也不需要调用autorelease方法了,只需要用__autoreleasing修饰变量即可。


有了ARC之后,新的property modifier也被引入到Objective-C类的property,例如:

@property (strong, nonatomic) NSString *text;

下面有张表来展示property modifier与ownership qualifier的对应关系

iOS 内存泄漏监测自动化:http://www.cocoachina.com/ios/20170102/18490.html

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

推荐阅读更多精彩内容