iOS 内存管理:从 MRC 到 ARC 实践
对于 iOS 程序员来说,内存管理是入门的必修课。引用计数、自动释放等概念,都是与 C 语言完全不同的。
iOS 内存管理的核心是引用计数。
接触 MRC 时遇到最头疼的问题就是:为什么那么多 release?什么地方需要 release?
同样初始化一个字符串的两个方法为什么不同,上边一个不需要调用 release,后边一个就需要调用 release?
NSString * str1 = [NSString stringWithFormat:"qqstock"];
NSString * str2 = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
属性赋值与成员变量赋值,一个导致计数器 +1,一个就不会。
self.name = @"qqstock";
_name = @"qqstock";
一、MRC 的初衷和实现方式
苹果为什么要做这个?
首先,C/C++ 传统的内存管理方式是:所有的内存都需要业务代码自己处理,程序员一定要知道一个内存对象什么时候不再使用了,一定要知道这个内存对象的终点在哪里。
当代码越来越复杂,参与开发的程序员越来越多,甚至随着岁月的流逝更换了新的程序员,这个时候,很难有人说的清了。于是,要么那个内存对象一直留在那里,没人敢释放,整个程序占用的空间越来越大;要么,一个胆大的程序员将它释放掉,某处发生了crash。
尽管大家总结出许多类似“谁创建谁释放”、“谁持有谁释放” 的原则,但都导致存储空间的浪费:为了保留仅仅一个内存对象,却要将与它关联的一大堆对象保留住,而其中大部分已经不再使用了。要么,自己写许许多多的代码,频繁对容器进行主动操作。
于是,苹果要解决这个问题。初衷就是:任何一个内存对象由系统自己处理释放的问题,无论创建者也好,持有者也好,不需要去考虑别人是否还在使用同一个内存对象,做好自己该做的就是了,别人的事情别人负责。
苹果实现此目的的手段就是引用计数。所有使用到同一内存对象的地方,使用者只要保证自己 retain 一次,release 一次,就好了,即便别人还在使用,你只要调用 release 将自己的引用次数清零就好了,不用管别人。
与 C/C++ 传统的内存管理方式相比,MRC 是不是显得非常智能?是不是更加方便?而且,这样做的代价也非常低廉,每一个内存对象增加一个计数器就好了,每一次 release,只需要检查一遍计数器是否为零,如果为零就释放,如果不为零就不执行真正的释放逻辑。
另外,为了解决函数返回值的问题,需要搞一个 autorelease 的东西,否则就会打破这个良好的初衷:“只负责自己范围内的事情就好了,不要管别人。”
那么为什么不将所有内存对象都统一成 retain 呢?对于一种编译器,它能够用一个技术解决所有问题,就坚决不会用两种并列的技术导致问题更复杂。
OC 有一个 delegate 的东西,这个东西的出现也是有其现实需求的,在此先跳过。如果所有地方都使用 retain,delegate 的问题一定会导致循环引用,除了 delegate,苹果不敢保证所有用户代码的逻辑都是树形结构的,最简单的比如说循环链表、双向链表,除此之外,业务层肯定也有某些地方必须做成“循环引用”,如果都是 retain,那么,最终处于循环中的内存对象谁也不会被最终释放掉。为了解决这个问题,苹果依然保留了 C/C++ 的那种弱引用方式。
二、MRC 的优点和无奈
总结:
- MRC 的计数器机制改善了内存管理的方式,减少了各个模块的逻辑耦合,释放了程序员对“何时该释放”的心理压力,解决了大部分的问题
- 为了应对各种复杂的场景,很无奈的留了一个口子;
- 两种模式的并存,对 C++ 程序员转移到 OC 树立了一个无形的心理门槛,使得起步阶段问题更加复杂,比如:retain、assign、release、autorelease 等。
难道就没有更好的方式么?当然有更好的方式,而且一定有许多公司的 C++ 程序员或者 C 程序员写了类似引用计数的程序,甚至比引用计数还要高级,只不过大多数公司没有实力推广一个编程语言而已。
略微深入思考,一定许多人想到:如果让系统对所有内存对象在运行时统一管理,问题就能彻底解决了。是的,的确如此,一定有人设计出来了。但是,代价比较高。
系统在运行时统一管理所有内存对象的释放,会导致增加额外的内存和 CPU 开销,在硬件设备尚且处于低级阶段的时候,当程序员们依然在努力降低内存降低 CPU 消耗的时候,推出这样的机制,是不合时宜的!
引用计数器的方式,编译器并没有增加太多的逻辑,只是在创建的时候增加一个计数器,在释放的时候编译器自动帮程序员增加一个逻辑判断。这个逻辑并没有增加太多的内存和 CPU 开销。
再来看 autorelease,这个逻辑增加的成本可就大了去了,系统要一直持有该类型的内存对象,直到本次 runloop 结束。所以,无论苹果,还是有经验的程序员,都建议:能不用就尽量不用,能缩短范围就尽量缩短范围。
三、编程语言和编译器的发展方向
由于留了无奈的口子,野指针依然会出现,该 crash 的时候依然 crash。许多人说:这是程序员的问题,如果代码写的足够好,一定不会出现野指针,一定不会出现 crash。是的,如果大家足够尽力,这个世界上不会有任何冲突。
然而,编程语言和编译器的发展,一定向着便利、易用、稳健、智能,甚至傻瓜。如果一个编译器能够让一个对计算机毫无了解的人一天之内搞出自己想要的业务应用,谁又会拒绝呢?
许多程序员都是技术控,自己能做的事情尽量不让别人做,自己能实现的逻辑尽量不用别人的。比如:C++ 的各种封装、引用,我用 C 也能实现,有什么大不了的!系统提供的各种类库,我自己用底层的代码也能实现,而且性能更优,代码更少。但是,如果你连一个砖头都要自己烧制,连一堵墙都要自己去砌,其它更重要的事情谁去做?
更何况,人,总有打盹的时候。
四、ARC 的适时推出
随着硬件的升级,条件已经成熟了,ARC 到来了!
ARC 的初衷是为了让程序员写代码的时候更加便利,最好不用再关注任何内存释放的问题(也不用关注用什么方式初始化的问题)。当然了,解决野指针的问题也是很重要的!总之,让编码更加简单,程序更加健壮。
之前对 C++ 程序员头疼的问题变得异常简单:
NSString * str1 = [NSString stringWithFormat:"qqstock"];
NSString * str2 = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
self.name = @"qqstock";
_name = @"qqstock";
到底何时释放?总之,你不用管了,用就好。到底有何区别?没啥区别,只管用就好。
许多刨根究底的程序员从汇编代码也印证了这个“猜想”。ARC 的目的就是将程序员从 MRC 的各种“不同点”上解脱出来,对于尚未接触过 MRC 的 C 程序员是非常容易理解的,而对于已经习惯了 MRC 的程序员,反倒有点“不敢相信”。
逻辑其实很简单。
首先,强引用依然保留 MRC 的方式,因为这样实现的方式代价很低;
其次,一旦出现弱引用,则将内存对象在系统中建立映射表;一旦内存对象因为所有强引用归零而释放,则将所有弱引用指针归零(指向 nil)。其实,将弱引用强制指向 nil 也是一种无奈的方式,按理说,这依然是个隐患,是代码逻辑的缺陷,只是人家帮你将错误的代价降到最低而已。
总之,强引用的逻辑是:如果都不用了,我就释放掉;弱引用的逻辑是:如果释放了,我就置 nil。最终,程序员不需要关注内存的持有和释放问题,更不需要关注别的模块是否依然在使用同一个内存。做好自己分内的事情,别的事情交给系统和编译器!
五、总结
如果所有地方都用强引用,或者所有地方都交予系统管理,势必会导致内存的快速膨胀。某些其它语言的例子就非常明显,无论程序员如何努力,内存也很难降低下来。
许多问题,如果我们能够站在设计者的立场上考虑,就能够更加清楚自己该如何 coding,设计者的初衷决定了我们 coding 的方式,设计者的 coding 决定了我们的思维方式。
①、使用 retain 类型初始化方法给 weak 和 assign 类型变量赋值时,编译器会报警。
②、weak 变量当其指向的变量的所有强引用置零后,自己会被置 nil,而 assign 却不会。
③、weak 变量被置 nil,不是当其指向变量析构的时候,而是在强引用归零的时候就已经发生了。
④、各种类方法初始化的 autorelease 对象,依然是在 runloop 结束的时候析构的,而 retain 类型的对象,却是在代码模块终止的时候析构的。所以,出于内存管理的考虑,依然建议少用 autorelease。
iOS 内存管理:从 MRC 到 ARC 实践的更多相关文章
- OC 知识:彻底理解 iOS 内存管理(MRC、ARC)
1. 什么是内存管理 程序在运行的过程中通常通过以下行为,来增加程序的的内存占用 创建一个OC对象 定义一个变量 调用一个函数或者方法 而一个移动设备的内存是有限的,每个软件所能占用的内存也是有限的 ...
- IOS内存管理「4」- ARC 和垃圾回收机制的基本概念
- 【Bugly干货分享】iOS内存管理:从MRC到ARC实践
Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 对于iOS程序员来说,内存管理是入门的 ...
- iOS内存管理 ARC与MRC
想驾驭一门语言,首先要掌握它的内存管理特性.iOS开发经历了MRC到ARC的过程,下面就记录一下本人对iOS内存管理方面的一些理解. 说到iOS开发,肯定离不开objective-c语言(以下简称OC ...
- iOS内存管理策略和实践
转:http://www.cocoachina.com/applenews/devnews/2013/1126/7418.html 内存管理策略(memory Management Policy) N ...
- iOS内存管理机制解析之MRC手动引用计数机制
前言: iOS的内存管理机制ARC和MRC是程序猿參加面试基本必问的问题,也是考察一个iOS基本功是 否扎实的关键,这样深入理解内存管理机制的重要性就不言而喻了. iOS内存管理机制发展史 iOS 5 ...
- iOS内存管理
iOS内存管理的方式是引用计数机制.分为MRC(人式引用计数)和ARC(自动引用计数). 为什么要学习内存管理? 内存管理方式是引用计数机制,通过控制对象的引用计数来实现操作对象的功能.一个对象的生命 ...
- IOS内存管理学习笔记
内存管理作为iOS中非常重要的部分,每一个iOS开发者都应该深入了解iOS内存管理,最近在学习iOS中整理出了一些知识点,先从MRC开始说起. 1.当一个对象在创建之后它的引用计数器为1,当调用这个对 ...
- 75.iOS内存管理
堆区和栈区 1.栈区:由编译器自动分配释放,函数的参数值,局部变量等值 2.堆区:一般由开发人员分配释放,若不释放,则可能会引起内存泄漏 NSString *string = @"abcd& ...
随机推荐
- PyCharm+git+码云实现project版本控制
1.安装git https://git-scm.com/downloads 2.PyCharm中配置 3.申请码云 4.PyCharm中安装码云插件 右键选择,重启Pycharm. 重新打开PyCha ...
- 小技巧(一):将文本文件txt或网页快捷方式固定到win10开始菜单
win10不知道怎么回事不支持将文本文件和网页快捷方式固定到开始菜单 解决方法: 利用cmd 创建一个快捷方式: 路径:cmd /A /C C:\Users\Admin\Desktop\test.t ...
- Vue方式自动打开浏览器配置
Vue方式自动打开浏览器配置 1. 通过 package.json 配置项目 // 必须是符合规范的json语法 "vue": { "devServer": { ...
- VUE实现Studio管理后台(十三):按钮点选输入控件,input输入框系列
按钮点选输入,是一个非常简单的控件,20分钟就能完成的一个控件.先看效果: 根据以前的设定,通过json数据动态生成这两个按钮,示例中这两个按钮对应的json代码: { label:'标题', val ...
- 小程序的数据存储,与Django等服务发送请求
目录 官方文档 快速归纳 存取改删 1.wx存储数据到本地以及本地获取数 1.1 wx.setStorageSync(string key, any data) 存(同步) 1.2 wx.setSto ...
- 01 ORM框架概述
ORM概述 ORM(Object-Relational Mapping) 表示对象关系映射.在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中.只要有一套程序能够做到建立对象与数据库 ...
- MongoDB复制集概念架构浅析
一.复制集的作用 (1) 高可用 防止设备(服务器.网络)故障. 提供自动failover 功能. 技术来保证数 (2) 灾难恢复 当发生故障时,可以从其他节点恢复. (3) 功能隔离 用于分析.报表 ...
- guava限流器RateLimiter原理及源码分析
前言 RateLimiter是基于令牌桶算法实现的一个多线程限流器,它可以将请求均匀的进行处理,当然他并不是一个分布式限流器,只是对单机进行限流.它可以应用在定时拉取接口数据, 预防单机过大流量使用. ...
- des 加密解密工具类
最近在做des的双对称加密解密,特此记录一下. des对称加密,是一种比较传统的加密方式,其加密运算.解密运算使用的是同样的密钥,信息的发送者和信息的接收者在进行信息的传输与处理时,必须共同持有该密码 ...
- touch事件中的touches、targetTouches和changedTouches
touches: 当前屏幕上所有触摸点的列表; targetTouches: 当前对象上所有触摸点的列表; changedTouches: 涉及当前(引发)事件的触摸点的列表; 通过一个例子来区分一下 ...