iOS内存管理技术

Copyright © 2017年ZaneWangWang. All rights reserved.

如果你看到的不是原文请点击查看原文

一.内存管理是什么

        内存管理,是指软件运行时对内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。

        iOS内存管理的本质是管理对象的引用计数,对象的引用计数就是标记当前有多少个其他对象使用(拥有)这个对象。分为自动内存管理MRC(manualreference counting)和ARC(automatic reference counting iOS5.0推出)。

       手动管理内存就是当使用alloc/new/copy/retain,创建或引用一个对象时会使对象的引用计数+1。当不使用对象当向对象发送release或autorelease消息会使对象的引用计数-1。当一个对象的引用计数为0时,那么它将被销毁,其占用的内存被系统回收,OC会自动向其发送一条dealloc消息(自动调用dealloc方法),我们此时可以重写dealloc方法,在这里释放相关资源。一旦某个对象的引用计数为0,那么它就会被系统释放掉。只要对象的引用计数不为0,它就在内存中永远不会被释放, 这样就会造成内存泄露。

      自动管理内存是编译时的特性,不需要我们手动retain或者release,系统会在适当的时候为我们添加相应的代码。但是自动管理内存需要我们注意循环引用以防止内存泄漏。在后边内容中我们会详细说明导致循环引用的情况和解决的方法。

二.为什么要进行内存管理

       手机的内存是有限的,如果我们在需要使用一个对象的时候创建了对象,而不需要使用了却不把他释放掉,内存的使用量会越来越多,导致系统内存资源的浪费,影响程序和系统运行的流畅度.

三.内存管理要怎么做

1.首先介绍一下不同语言的内存管理方式

JAVA:全自动管理内存,程序运行时每隔一段时间就会自动检查内存当中的所有对象,当发现某个对象不再使用时,就会把它从内存中释放掉.这个功能称为JAVA垃圾回收机制(它是运行时的特性).这种内存管理机制使用方便,但是效率很低;

c++:全手动管理内存,当需要是创建,确定不再使用的时候手动通过free函数把它释放掉.这种内存管理方式使用麻烦,但是运行效率高;

OC:内存管理{既可以自动管理内存ARC(它是编译时的特性)(automatic reference counting)也可以手动管理内存MRC(manual reference counting)},范围:任何继承与NSObject的对象都需要内存管理.

2.oc内存管理

通过引用计数来管理内存:对象的引用计数就是标记当前有多少个其他对象使用(拥有)这个对象。

当使用alloc/new/copy/retain,创建或引用一个对象时会使对象的引用计数+1

当向对象发送release或autorelease消息会使对象的引用计数-1

当一个对象的引用计数为0时,那么它将被销毁,其占用的内存被系统回收,OC会自动向其发送一条dealloc消息(自动调用dealloc方法),我们此时可以重写dealloc方法,在这里释放相关资源。

一旦某个对象的引用计数为0,那么它就会被系统释放掉。

只要对象的引用计数不为0,它就在内存中永远不会被释放, 这样就会造成内存泄露

IOS 5.0之后,OC又提供了自动管理机制,ARC(automatic reference counting)      MRC(manualreference counting)

3.ARC 和 MRC 的混编。

需要我们在(工程名—>TARGETS—>BuildPhases—>Compile Sources)位置配置这些代码ARC下使用MRC文件,-fno-objc-arcMRC下使用ARC文件,-fobjc-arc

4. MRC下的内存管理

本质上是对对象的引用计数的管理,管理原则是:谁创建谁释放,谁保留谁管理

alloc new retain  copy会使对象的引用计数+1

release  autorelease会让对象的引用计数-1

程序员要对自己alloc retain new copy的负责

系统的方法导致的引用计数加一,系统不用的时候他会自己将增加部分减掉

1).将对象添加到数组或字典中(引用计数加一)

2).addSubView(引用计数加一)

3). xib拖拽的控件(引用计数为三  alloc retain addSubView)

4).将视图控制器对象放入导航控制器中

5).将视图控制器对象作为window的rootViewController

6).开启定时器

释放时机:

1).创建局部对象用完释放

2).全局变量delloc方法中释放

3).方法内创建的对象,在方法外使用,使用autorelease

4).通过静态方法或者字面量方式创建的对象是通过autorelease

的方法延时释放(要想使用,要保留一次引用计数,并且要在dealloc中释放)

5).在dealloc中实例变量的释放要写在[super dealloc];前边(本类对象销毁的时候调用)

内存错误的两总情况:

1).内存泄露:对象的引用计数始终不为0,一直留在内存中(指针不在,对象存在)

2).过度释放使用一个对象的时候,对象已经被释放掉了,过度释放会造成程序崩溃(EXC_BAD_ACCESS坏的访问)(指针存在,对象不再)

MRC下setter方法(copy只需将retain换成copy):

-(void)setDog:(ZZWDog*)aDog

{

if(_dog!= aDog) {//判断防止重新赋值对象与上一次一样的时候对象释放无法赋值导致过度释放

[_dogrelease];//重新赋值之前先release,防止上次的内存泄露

_dog=[aDogretain];//使它加一

}

}

注意的小点儿:

//数组等一些类在加号方法创建的时候,系统加号方法创建对象的时候对象已经写进自动释放池(alloc +autorelease),不用管理内存

//注意:静态方法创建的对象会在什么时间释放,通过字面量创建的也是如此

//实例变量释放要写在[super dealloc]的上方,跟init初始化相反

//谁创建谁释放在哪个方法alloc出来的对象,就在哪个方法中release 方法内alloc 把对象放进自动释放池对象调用autorelease方法就会将该对象加入自动释放池(autorelease pool)中,自动释放池销毁的时候,该池子中的对象就会调用一次release方法。自动释放池会在将来一个适当的时候销毁。

//不能将定时器的损毁写在delloc方法中,这是因为只要定时器不损毁,当前视图控制器对象self就不会销毁,也就不会调用delloc方法

//非容器类属性写copy主要是针对可变字符串的,当可变字符串给属性赋值时,写的retain,那么属性跟着字符串变化,如果写的是copy,在执行set方法时就会传过来的可变字符串实现深拷贝,生成一个不可变的字符串,这样就保证属性的值就不会受原来字符串影响

5.ARC下的内存管理

ARC自动引用计数管理,不需要我们写retain,release,autorelease等代码,系统在编译的时候会帮我们在合适的位置插入这些代码.

6.ARC修饰符

ARC主要提供了4种修饰符,他们分别是:__strong,__weak,__autoreleasing,__unsafe_unretained。

__strong

表示引用为强引用。对应在定义property时的"strong"。所有对象只有当没有任何一个强引用指向时,才会被释放。

注意:如果在声明引用时不加修饰符,那么引用将默认是强引用。当需要释放强引用指向的对象时,需要将强引用置nil。

__weak

表示引用为弱引用。对应在定义property时用的"weak"。弱引用不会影响对象的释放,即只要对象没有任何强引用指向,即使有100个弱引用对象指向也没用,该对象依然会被释放。不过好在,对象在被释放的同时,指向它的弱引用会自动被置nil,这个技术叫zeroing weak pointer。这样有效得防止无效指针、野指针的产生。

__weak一般用在

1,delegate关系中防止循环引用

2,用来修饰指向由Interface Builder编辑与生成的UI控件

3,防止block的循环引用。

__autoreleasing

表示在autorelease pool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。

一个常见的误解是,在ARC中没有autorelease,因为这样一个“自动释放”看起来好像有点多余。这个误解可能源自于将ARC的“自动”和autorelease“自动”的混淆。其实你只要看一下每个iOS App的main.m文件就能知道,autorelease不仅好好的存在着,并且变得更fashion了:不需要再手工被创建,也不需要再显式得调用[drain]方法释放内存池。


以下两行代码的意义是相同的。

NSString *str = [[[NSString alloc] initWithFormat:@"hehe"] autorelease];//MRCNSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"hehe"];//ARC

这里关于autoreleasepool就不做展开了,详细地信息可以参考官方文档或者其他文章。

__autoreleasing在ARC中主要用在参数传递返回值(out-parameters)和引用传递参数(pass-by-reference)的情况下。

__autoreleasingis used to denote arguments that are passed by reference (id *) and are autoreleased on return.

比如常用的NSError的使用:

NSError *__autoreleasing error;

if(![data writeToFile:filename options:NSDataWritingAtomic error:&error])

{

NSLog(@"Error: %@", error);

}

(在上面的writeToFile方法中error参数的类型为(NSError *__autoreleasing *))

注意,如果你的error定义为了strong型,那么,编译器会帮你隐式地做如下事情,保证最终传入函数的参数依然是个__autoreleasing类型的引用。

NSError *error;NSError *__autoreleasing tempError = error;//编译器添加

if(![data writeToFile:filename options:NSDataWritingAtomic error:&tempError])

{

error = tempError;//编译器添加

NSLog(@"Error: %@", error);

}

所以为了提高效率,避免这种情况,我们一般在定义error的时候将其(老老实实地=。=)声明为__autoreleasing类型的:

NSError *__autoreleasing error;

在这里,加上__autoreleasing之后,相当于在MRC中对返回值error做了如下事情:

*error = [[[NSError alloc] init] autorelease];

*error指向的对象在创建出来后,被放入到了autoreleasing pool中,等待使用结束后的自动释放,函数外error的使用者并不需要关心*error指向对象的释放。

另外一点,在ARC中,所有这种指针的指针 (NSError **)的函数参数如果不加修饰符,编译器会默认将他们认定为__autoreleasing类型。

比如下面的两段代码是等同的:

- (NSString *)doSomething:(NSNumber **)value

{//do something}

- (NSString *)doSomething:(NSNumber * __autoreleasing *)value

{//do something}

除非你显式得给value声明了__strong,否则value默认就是__autoreleasing的。

最后一点,某些类的方法会隐式地使用自己的autorelease pool,在这种时候使用__autoreleasing类型要特别小心。

比如NSDictionary的[enumerateKeysAndObjectsUsingBlock]方法:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error

{

[dict enumerateKeysAndObjectsUsingBlock:^(idkey,idobj, BOOL *stop){//do stuffif(thereissome error && error !=nil)

{*error = [NSError errorWithDomain:@"MyError"code:1userInfo:nil];

}

}];}

会隐式地创建一个autorelease pool,上面代码实际类似于:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error

{

[dict enumerateKeysAndObjectsUsingBlock:^(idkey,idobj, BOOL *stop){@autoreleasepool//被隐式创建{if (thereissome error && error !=nil)

{*error = [NSError errorWithDomain:@"MyError"code:1userInfo:nil];              }}}];//*error 在这里已经被dict的做枚举遍历时创建的autorelease pool释放掉了 :(}

为了能够正常的使用*error,我们需要一个strong型的临时引用,在dict的枚举Block中使用这个临时引用,保证引用指向的对象不会在出了dict的枚举Block后被释放,正确的方式如下:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error

{

__blockNSError* tempError;//加__block保证可以在Block内被修改

[dict enumerateKeysAndObjectsUsingBlock:^(idkey,idobj, BOOL *stop)

{

if(thereissome error)

{

*tempError = [NSError errorWithDomain:@"MyError"code:1userInfo:nil];

}

}]

if(error !=nil)

{

*error =tempError;

}

}

__unsafe_unretained

ARC是在iOS 5引入的,而这个修饰符主要是为了在ARC刚发布时兼容iOS 4以及版本更低的设备,因为这些版本的设备没有weak pointer system,简单的理解这个系统就是我们上面讲weak时提到的,能够在weak引用指向对象被释放后,把引用值自动设为nil的系统。这个修饰符在定义property时对应的是"unsafe_unretained",实际可以将它理解为MRC时代的assign:纯粹只是将引用指向对象,没有任何额外的操作,在指向对象被释放时依然原原本本地指向原来被释放的对象(所在的内存区域)。所以非常不安全。

现在可以完全忽略掉这个修饰符了,因为iOS 4早已退出历史舞台很多年。

*使用修饰符的正确姿势(方式=。=)

这可能是很多人都不知道的一个问题,包括之前的我,但却是一个特别要注意的问题。

苹果的文档中明确地写道:

You should decorate variables correctly. When using qualifiers in an object variable declaration,

the correct format is:

ClassName * qualifier variableName;

按照这个说明,要定义一个weak型的NSString引用,它的写法应该是:

NSString * __weak str =@"hehe";//正确!

而不应该是:

__weak NSString *str =@"hehe";//错误!

我相信很多人都和我一样,从开始用ARC就一直用上面那种错误的写法。

那这里就有疑问了,Other variants are technically incorrect but are “forgiven” by the compiler. To understand the issue, seehttp://cdecl.org/.

好吧,看来是苹果爸爸 考虑到很多人会用错,所以在编译器这边贴心地帮我们忽略并处理掉了这个错误:)虽然不报错,但是我们还是应该按照正确的方式去使用这些修饰符,如果你以前也常常用错误的写法,那看到这里记得以后不要这么写了,哪天编译器怒了,再不支持错误的写法,就要郁闷了。

栈中指针默认值为nil

无论是被strong,weak还是autoreleasing修饰,声明在栈中的指针默认值都会是nil。所有这类型的指针不用再初始化的时候置nil了。虽然好习惯是最重要的,但是这个特性更加降低了“野指针”出现的可能性。

在ARC中,以下代码会输出null而不是crash:)

- (void)myMethod

{

NSString*name;

NSLog(@"name: %@", name);

}

7.ARC与Block

在MRC时代,Block会隐式地对进入其作用域内的对象(或者说被Block捕获的指针指向的对象)加retain,来确保Block使用到该对象时,能够正确的访问。

这件事情在下面代码展示的情况中要更加额外小心。

MyViewController *myController =[[MyViewController alloc] init…];//隐式地调用[myController retain];造成循环引用myController.completionHandler =  ^(NSInteger result) {

[myController dismissViewControllerAnimated:YES completion:nil];

};

[self presentViewController:myController animated:YES completion:^{

[myController release];//注意,这里调用[myController release];是在MRC中的一个常规写法,并不能解决上面循环引用的问题}];在这段代码中,myController的completionHandler调用了myController的方法[dismissViewController...],这时completionHandler会对myController做retain操作。而我们知道,myController对completionHandler也至少有一个retain(一般准确讲是copy),这时就出现了在内存管理中最糟糕的情况:循环引用!简单点说就是:myController retain了completionHandler,而completionHandler也retain了myController。循环引用导致了myController和completionHandler最终都不能被释放。我们在delegate关系中,对delegate指针用weak就是为了避免这种问题。

不过好在,编译器会及时地给我们一个警告,提醒我们可能会发生这类型的问题:


对这种情况,我们一般用如下方法解决:给要进入Block的指针加一个__block修饰符。

这个__block在MRC时代有两个作用:

说明变量可改

说明指针指向的对象不做这个隐式的retain操作

一个变量如果不加__block,是不能在Block里面修改的,不过这里有一个例外:static的变量和全局变量不需要加__block就可以在Block中修改。

使用这种方法,我们对代码做出修改,解决了循环引用的问题:

MyViewController * __block myController =[[MyViewController alloc] init…];//...myController.completionHandler =  ^(NSInteger result) {

[myController dismissViewControllerAnimated:YES completion:nil];

};//之后正常的release或者retain

在ARC引入后,没有了retain和release等操作,情况也发生了改变:在任何情况下,__block修饰符的作用只有上面的第一条:说明变量可改。即使加上了__block修饰符,一个被block捕获的强引用也依然是一个强引用。这样在ARC下,如果我们还按照MRC下的写法,completionHandler对myController有一个强引用,而myController对completionHandler有一个强引用,这依然是循环引用,没有解决问题:(

于是我们还需要对原代码做修改。简单的情况我们可以这样写:

__block MyViewController * myController =[[MyViewController alloc] init…];//...myController.completionHandler =  ^(NSInteger result) {

[myController dismissViewControllerAnimated:YES completion:nil];

myController= nil;//注意这里,保证了block结束myController强引用的解除};

在completionHandler之后将myController指针置nil,保证了completionHandler对myController强引用的解除,不过也同时解除了myController对myController对象的强引用。这种方法过于简单粗暴了,在大多数情况下,我们有更好的方法。

这个更好的方法就是使用weak。(或者为了考虑iOS4的兼容性用unsafe_unretained,具体用法和weak相同,考虑到现在iOS4设备可能已经绝迹了,这里就不讲这个方法了)(关于这个方法的本质我们后面会谈到)

为了保证completionHandler这个Block对myController没有强引用,我们可以定义一个临时的弱引用weakMyViewController来指向原myController的对象,并把这个弱引用传入到Block内,这样就保证了Block对myController持有的是一个弱引用,而不是一个强引用。如此,我们继续修改代码:

MyViewController *myController =[[MyViewController alloc] init…];//...MyViewController * __weak weakMyViewController =myController;myController.completionHandler=  ^(NSInteger result) {

[weakMyViewController dismissViewControllerAnimated:YES completion:nil];

};

这样循环引用的问题就解决了,但是却不幸地引入了一个新的问题:由于传入completionHandler的是一个弱引用,那么当myController指向的对象在completionHandler被调用前释放,那么completionHandler就不能正常的运作了。在一般的单线程环境中,这种问题出现的可能性不大,但是到了多线程环境,就很不好说了,所以我们需要继续完善这个方法。

为了保证在Block内能够访问到正确的myController,我们在block内新定义一个强引用strongMyController来指向weakMyController指向的对象,这样多了一个强引用,就能保证这个myController对象不会在completionHandler被调用前释放掉了。于是,我们对代码再次做出修改:

MyViewController *myController =[[MyViewController alloc] init…];//...MyViewController * __weak weakMyController =myController;

myController.completionHandler=  ^(NSInteger result) {MyViewController*strongMyController =weakMyController;

if(strongMyController) {//...[strongMyController dismissViewControllerAnimated:YES completion:nil];//...}else{//Probably nothing...}

};

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

推荐阅读更多精彩内容