IOS内存管理

软件运行时会分配和使用设备的内存资源,因此,在软件开发的过程中,需要进行内存管理,以保证高效、快速的分配内存,并且在适当的时候释放和回收内存资源。

一、Objective-C内存管理的对象

iOS开发中,内存中的对象主要有两类,一类是值类型,比如int、float、struct等基本数据类型,另一类是引用类型,也就是继承自NSObject类的所有的OC对象。前一种值类型不需要我们管理,后一种引用类型是需要我们管理内存的,一旦管理不好,就会产生非常糟糕的后果。

为什么值类型不需要管理,而引用类型需要管理呢?那是因为他们分配内存方式不一样。值类型会被放入栈中,他们依次紧密排列,在内存中占有一块连续的内存空间,遵循先进后出的原则。引用类型会被放到堆中,当给对象分配内存空间时,会随机的从内存当中开辟空间,对象与对象之间可能会留有不确定大小的空白空间,因此会产生很多内存碎片,需要我们管理。

二、Objective-C管理内存的方式

Objective-C中提供了两种内存管理机制MRC和ARC,分别提供对内存的手动和自动管理,来满足不同的需求。

1.MRC(人工引用计数),手动管理内存。

MRC模式下,所有的对象都需要手动的添加retain、release代码来管理内存。使用MRC,需要遵守谁创建,谁回收的原则。也就是谁alloc,谁release;谁retain,谁release。

当引用计数为0的时候,必须回收,引用计数不为0,不能回收,如果引用计数为0,但是没有回收,会造成内存泄漏。如果引用计数为0,继续释放,会造成野指针。为了避免出现野指针,我们在释放的时候,会先让指针=nil。

2.ARC(自动引用计数),自动管理内存。

ARC是iOS5推出的新功能,通过ARC,可以自动的管理内存。在ARC模式下,只要没有强指针(强引用)指向对象,对象就会被释放。在ARC模式下,不允许使用retain、release、retainCount等方法。并且,如果使用dealloc方法时,不允许调用【super dealloc】方法。

ARC模式下的property变量修饰词为strong、weak,相当于MRC模式下的retain、assign。strong代替retain,缺省关键词,代表强引用。weak代替assign,声明了一个可以自动设置nil的弱引用,但是比assign多了一个功能,指针指向的地址被释放之后,指针本身也会自动被释放。

三、与内存有关的修饰符

strong:强引用,ARC中使用,与MRC中retain类似,使用之后,计数器+1。

weak: 弱引用,ARC中使用,如果只想的对象被释放了,其指向nil,可以有效的避免野指针,其引用计数为1.

readwrite:可读可写特性,需要生成getter方法和setter方法时使用。

readonly:只读特性,只会生成getter方法,不会生成setter方法,不希望属性在类外改变。

assign:赋值特性,不涉及引用计数,弱引用setter方法将传人参数赋值给实例变量,仅设置变量时使用。

retain:表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1。

copy:表示拷贝特性,setter方法将传入对象复制一份,需要完全一份新的变量时。

nonatomic:非原子操作,不加同步,多线程访问可提高性能,但是线程不安全的。决定编译器生成的setter,getter是否是原则操作。

atomic:原子操作,同步的,表示多线程安全,与nonatomic相反。

四、MRC与ARC混编

MRC与ARC理论上是不能兼容的,也就是你如果创建的项目是ARC模式的,在你的代码中是不能使用release,否则会出现内存问题。现在大部分程序都会选择ARC的方式,但是,很多第三方的框架是MRC模式,如果想把这些第三方的文件加到自己项目中,需要进行标识,否则编译的时候会出现错误。

在ARC的项目中,对MRC的文件可以添加编译选项-fno-objc-arc的标识;在MRC的项目中,对ARC的文件可以添加编译选项-fobjc-arc的标识。步骤如下图所示。


1.png

把MRC文件转为ARC,实际上就是去掉文件中的retain、release,因此也通过下图中方式完成。


2.png

五、关于内存管理经常会涉及的问题

1.僵尸对象、野指针、空指针分别指什么,有什么区别?

僵尸对象:一个OC对象引用计数为0被释放后就变成僵尸对象了,僵尸对象的内存已经被系统回收,虽然可能改对象还存在,数据依然在内存中,但僵尸对象已经是不稳定对象了,不可以再访问或者使用,它的内存是随时可能被别的对象申请而占用的;

野指针:野指针出现的原因是指针没有赋值,或者指针指向的对象已经被释放掉了,野指针指向一块随机的垃圾内存,向他们发送消息会报EXC_BAD_ACCESS错误导致程序崩溃;

空指针:空指针不同于野指针,它是一个没有指向任何东西的指针,空指针是有效指针,值为nil,NULL,或0等,给空指针发送消息不会报错,只是不想赢消息而已,应该给野指针及时赋予零值变成有效的空指针,避免内存报错。

2.Objectvie-C有GC垃圾回收机制吗?

GC(Garbage Collection),垃圾回收机制,简单地说就是程序中及时处理废弃不用的内存对象的机制,防止内存中废弃对象堆积过多造成内存泄漏。Objective-C语言本身是支持垃圾回收机制的,但有平台局限性,仅限于Mac桌面系统开发中,而在iPhone和iPad等苹果移动终端设备中是不支持垃圾回收机制的。在移动设备开发中的内存管理是采用MRC以及iOS5以后的ARC了,本质都是RC引用计数,通过引用计数的方式来管理内存的分配与释放,从而防止内存泄漏。

另外引用计数RC和垃圾回收GC是有区别的。垃圾回收是宏观的,对整体进行内存管理,虽然不同平台垃圾回收机制有异,但基本原理都是一样的:将所有对象看做一个集合,然后在GC循环中定时检测活动对象和非活动对象,及时将用不到的非活动对象释放掉来避免内存泄漏,也就是说用不到的垃圾对象是交给GC来管理释放的,而无需开发者关心,典型的是Java中的垃圾回收机制;相比于GC,引用计数是局部性的的,开发者要管理控制每个对象的引用计数,打个对象引用计数巍峨0后会马上被释放掉。ARC自动引用计数则是一种改进,由编译器帮助开发者自动管理控制引用计数(自动在核实的时机发送release和retain消息)。另外自动释放池autorelease pool则像是一个局部的垃圾回收,将部分垃圾对象几种释放,相对于单个释放会有一定延迟。

相关问题:自动释放池跟GC(垃圾回收)有什么区别?iPhone上有GC吗?[pool release]和[pool drain]有什么区别?

[pool release]和[pool drain]在作用上是一样的,都是倾倒自动释放池,区别是drain在支持GC垃圾回收的系统中(Mac系统)可以引起GC回收操作,而release不可以。对于自动释放池一般还是使用[pool drain]了,一是它的功能对系统兼容性更强,二者也是为了跟普通对象的release释放区别开。了自动释放池的释放操作指的是向池内所有的对象发送release消息,以让系统及时释放池内的所有对象。

3.如果一个对象释放前被加到了NotificationCenter中,不在NotificationCenter中remove这个对象可能回出现什么问题?

首先对于NotificationCenter的使用,我们都知道,只要添加对象到消息中心进行通知注册,之后就一定要对其remove进行通知注销。将对象添加到消息中心后,消息中心只是保存该对象的地址,消息中心到时候回根据地址发送通知给该对象,但并没有取得该对象的强引用,对象的引用计数不会加1。如果对象释放后却没有从消息中心remove掉进行通知注销,也就是通知中心还保存着那个指针,而那个指针的对象可能已经被释放销毁了,那个指针就成为一个野指针,当通知发生时,会向这个野指针发送消息导致程序崩溃。

4.Objective-C是如何实现内存管理的? autorelease pool自动释放池是什么? autorelease的对象是在什么时候被release的? autorelease和release有什么区别?

引用计数

Objective-C的内存管理本质上是通过引用计数实现的,每次Runloop都会检查对象的引用计数,如果引用计数为0,说明该对象已经没人用了,可以对其进行释放了。其中引用计数可以大体分三种:MRC(手动内存计数),ARC(自动内存计数,iOS5以后)和内存池。

其中引用计数是如何操作的呢?不论哪种引用计数方式,本质都是在合适的时机将对象的引用计数加1或者减一。

这里简单归结一下:

使对象引用计数加1的常见操作有:alloc,copy,retain

使对象引用计数减1的常见操作有:release,autorelease

自动释放池

自动释放池是一个统一来释放一组对象的容器,在向对象发送autorelease消息时,对象并没有立即释放,而是将对象加入到最新的自动释放池(即将该对象的引用交给自动释放池,之后统一调用release),自动释放池会在程序执行到作用域结束的位置时进行drain释放操作,这个时候会对池中的每一个对象都发送release消息来释放所有对象。这样其实就实现了这些对象的延迟释放。

自动释放吃释放的时机,也就是自动释放池内的所有对象是在什么时候释放的,这里要提到程序的运行周期RunLoop。对于每一个新的RunLoop,系统都会隐式的创建一个autorelease pool,RunLoop结束时自动释放池变回进行对象释放操作。

autorelease和release的区别主要是引用计数减一的时机不同,autorelease会在对象的使用真正结束的时候才做引用计数减1,而不是收到消息立马释放。

retain,release和autorelease的底层实现

最后通过了解这三者的较底层实现来理解它们的本质区别:

-(id)retain{

//对象引用计数加1

NSIncrementExtraRefCount(self);

return self;

}

-(void)release{

//对象引用计数减1,之后如果引用计数为0则释放

if(NSDecrementExtraRefCountWasZero(self)){

NSDeallocateObject(self);

}

}

-(id)autorelease{

//添加对象到自动释放池

【NSAutoreleasePool addObject:self];

return self;

}

5.问题:为什么很多内置的类,如TableViewController的delegate的属性是assign不是retain?

delegate代理的属性通常设置为assign或者weak是为了避免循环引用,所有的引用计数系统,都存在循环引用的问题,但也有个别特殊情况,个别类的代理例如CAAnimation的delegate就是使用strong强引用。

其它问法:委托的property声明用什么属性?为什么?

6.CAAnimation的delegate代理是强引用还是弱引用?

CAAnimation的代理是强引用,是内存管理中的其中一个罕见的特例。我们知道为了避免循环引用问题,delegate代理一般都使用weak修饰表示弱引用的,而CAAnimation动画是异步的,如果动画的代理是弱引用不是强引用的话,会导致其随时都可能被释放掉。在使用动画时要注意采取措施避免循环引用,例如及时在试图移除之前的合适时机移除动画。

CAAnimation的代理定义如下,明确说了动画的代理在动画对象整个生命周期是被强引用的,默认为nil。

7.问题:OC中,与alloc语义相反的方法是dealloc还是release?与retain语义相反的方法是dealloc还是release?需要与alloc配对使用的方式dealloc还是release,为什么?

alloc与dealloc语义相反,alloc是创建变量,dealloc是释放变量;

retain与release语义相反,retain保留一个对象,调用后使变量的引用计数加1,而release释放一个对象,调用后使变量的引用计数减1。

虽然alloc对应dealloc,retain对应release,但是与alloc配对使用的方法是release,而不是dealloc,为什么呢?这要从他们的实际效果来看。事实上alloc和release配对使用知识表象,本质上其实还是retain和release的配对使用。alloc用来创建对象,刚创建的对象默认引用计数为1,相当于调用alloc创建对象过程中同时会调用一次retain使对象引用计数加1,自然要有对应的release的一次调用,使对象在不用时能够被释放掉防止内存泄漏。

此外,dealloc是在对象引用计数为0以后系统自动调用的,dealloc没有使对象引用计数减1的作用,只是在对象引用计数0后被系统调用进行内存回收的收尾工作。

8.内存管理的几条原则是什么? 按照默认法则,哪些关键字生成的对象需要手动释放?哪些对象不需要手动释放会自动进入释放池?在和property结合的时候怎样有效的避免内存泄漏?

起初MRC中开发者要自己手动管理内存,基本原则是:谁创建,谁释放,谁引用,谁管理。其中创建主要始于关键词new,alloc和copy的使用,创建并持有开始引用计数为1,如果引用要通过发送retain消息增加引用计数,通过发送release消息减少引用计数,引用计数为0后对象会被系统清理释放。现在有了ARC后编译器会自动管理引用计数,开发者不再需要也不可以再手动显示管理引用计数。

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

推荐阅读更多精彩内容

  • 从上图可以看到,栈里面存放的是值类型,堆里面存放的是对象类型。对象的引用计数是在堆内存中操作的。下面我们讲讲堆和栈...
    jackyshan阅读 1,651评论 2 11
  • Copyright © 2017年ZaneWangWang. All rights reserved. 如果你看到...
    2897275c8a00阅读 917评论 0 1
  • 自动引用计数 什么是自动引用计数内存管理/引用计数ARC规则ARC的实现 1.1 什么是自动引用计数 ARC和MR...
    凡几多阅读 899评论 0 5
  • 为什么管理内存: 程序在运行的时候,要创建大量的对象,这些对象放在堆和栈上。(基本类型放在栈上,由系统自动管理。)...
    我是谁重要吗阅读 1,376评论 0 12
  • 冯·诺依曼体系:运算器 控制器 存储器 输入与输出 内存即存储器,用来存储指令与数据 注:哈佛体系与普林斯顿体系的...
    小李龍彪阅读 656评论 0 8