今天学习了IOS中的内存管理机制,其中一些片段是由其他文章搬过来的,我记得有篇文章是这样写到,站在巨人的肩膀上,我只是一个整理者加了一些自己的理解
虽然 ARC 帮我们解决了引用计数的大部分问题,一些年轻的 iOS 开发者仍然会做不好内存管理工作。他们甚至不能理解常见的循环引用问题,而这些问题会导致内存泄漏,最终使得应用运行缓慢或者被系统终止进程。
官方解释:软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源
1、知识内存区分
我们的智能设备都有RAM(运行内存)和ROM(硬盘)。
RAM:内部存储
ROM:外部存储。
而CPU直接访问的是RAM,如果想访问外部存储,则数据须先放到RAM中才能被CPU访问。
2、RAM和ROM的特点和区别
RAM:运行内存,CPU可以直接访问,访问速度快,不能够断电存储(断电会丢失数据–不稳定)
ROM:存储型内存,CPU不可以直接访问,访问速度慢,可以掉电存储–稳定
3、RAM和ROM的协同工作
由于RAM不支持掉电存储,所以App程序一般存储在ROM中。
手机里面使用的ROM基本都是NandFlash(闪存),CPU是不能直接访问的,而是需要 文件系统/驱动程序 将其读到RAM里面,CPU才可以访问的。
内存即指的是RAM,可以分为5个区域
我们需要管理的是堆上的内存
通常来说:当创建(alloc)一个对象的实例并在堆上申请内存时,对象的引用计数就为1,在其他对象中需要持有(retain)这个对象时,就需要把该对象的引用计数加1,需要释放一个对象时,就将该对象的引用计数减1,直至对象的引用计数为0,对象的内存会被立刻释放。
很文艺的解释是怎样的呢?
记得在《寻梦环游记》里对于一个人的死亡是这样定义的:当这个这个世界上最后一个人都忘记你时,就迎来了终极死亡。类比于引用计数,就是每有一个人记得你时你的引用计数加1,每有一个人忘记你时,你的引用计数减1,当所有人都忘记你时,你就消失了,也就是从内存中释放了。
如果再深一层,包含我们后面要介绍的ARC中的强引用和弱引用的话,那这个记住的含义就不一样了。强引用就是你挚爱的亲人,朋友等对你比较重要的人记得你,你的引用计数才加1。
而弱引用就是那种路人,一面之缘的人,他们只是对你有一个印象,他们记得你是没有用的,你的引用计数不会加1。当你挚爱的人都忘记你时,你的引用计数归零,你就从这个世界上消失了,而这些路人只是感觉到自己记忆中忽然少了些什么而已。
对象操作 | 引用计数的变化 |
---|---|
alloc/new/copy/mutableCopy等 | +1 |
retain | +1 |
release | -1 |
dealloc | - |
法则:
/*
* 自己生成并持有该对象
*/
id obj0 = [[NSObeject alloc] init];
id obj1 = [NSObeject new];
/*
* 持有非自己生成的对象
*/
id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有
[obj retain]; // 自己持有对象
/*
* 不在需要自己持有的对象的时候,释放
*/
id obj = [[NSObeject alloc] init]; // 此时持有对象
[obj release]; // 释放对象
/*
* 指向对象的指针仍就被保留在obj这个变量中
* 但对象已经释放,不可访问
*/
* 非自己持有的对象无法释放
*/
id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有
[obj release]; // ~~~此时将运行时crash 或编译器报error~~~ 非 ARC 下,调用该方法会导致编译器报 issues。此操作的行为是未定义的,可能会导致运行时 crash 或者其它未知行为
- (id) getAObjNotRetain {
id obj = [[NSObject alloc] init]; // 自己持有对象
[obj autorelease]; // 取得的对象存在,但自己不持有该对象
return obj;
}
使用autorelease方法可以使取得的对象存在,但自己不持有对象。autorelease 使得对象在超出生命周期后能正确的被释放(通过调用release方法)。在调用 release 后,对象会被立即释放,而调用 autorelease 后,对象不会被立即释放,而是注册到 autoreleasepool 中,经过一段时间后 pool结束,此时调用release方法,对象被释放。
像[NSMutableArray array] [NSArray array]都可以取得谁都不持有的对象,这些方法都是通过autorelease实现的。
Objective-C编程中为了处理对象,可将变量类型定义为id类型或各种对象类型。 ARC中id类型和对象类其类型必须附加所有权修饰符。
这里有一个很不错的解释放在这里。 所有权修饰符解释
这篇文章中对strong这样说到:为了便于理解,我们可以认为对象A是一条狗,obj是遛狗的人,强引用就是狗链子。当obj超出作用域的时候,链子断了,狗因为不再有链子拴着它,就逃跑了。
而我更认为更像是你复制了一条一摸一样的狗,再用链子栓住。
强烈建议大家亲自动手写写这里面代码,能更好的帮助你了解这里面的区别。
ARC 能够解决 iOS 开发中 90% 的内存管理问题,但是另外还有 10% 内存管理,是需要开发者自己处理的,这主要就是与底层 Core Foundation 对象交互的那部分,底层的 Core Foundation 对象由于不在 ARC 的管理下,所以需要自己维护这些对象的引用计数。
对于 ARC 盲目依赖的 iOS 新人们,由于不知道引用计数,他们的问题主要体现在:
循环引用(Reference Cycle)问题
引用计数这种管理内存的方式虽然很简单,但是有一个比较大的瑕疵,即它不能很好的解决循环引用问题。如下图所示:对象 A 和对象 B,相互引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减 1。因为对象 A 的销毁依赖于对象 B 销毁,而对象 B 的销毁与依赖于对象 A 的销毁,这样就造成了我们称之为循环引用(Reference Cycle)的问题,这两个对象即使在外界已经没有任何指针能够访问到它们了,它们也无法被释放。
不止两对象存在循环引用问题,多个对象依次持有对方,形式一个环状,也可以造成循环引用问题,而且在真实编程环境中,环越大就越难被发现。下图是 4 个对象形成的循环引用问题。
弱引用虽然持有对象,但是并不增加引用计数,这样就避免了循环引用的产生。在 iOS 开发中,弱引用通常在 delegate 模式中使用。举个例子来说,两个 ViewController A 和 B,ViewController A 需要弹出 ViewController B,让用户输入一些内容,当用户输入完成后,ViewController B 需要将内容返回给 ViewController A。这个时候,View Controller 的 delegate 成员变量通常是一个弱引用,以避免两个 ViewController 相互引用对方造成循环引用问题,如下所示:
弱引用的实现原理是这样,系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。
// 创建一个 CFStringRef 对象
CFStringRef str= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8);
// 创建一个 CTFontRef 对象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
对于这些对象的引用计数的修改,要相应的使用 CFRetain 和 CFRelease 方法。如下所示
// 创建一个 CTFontRef 对象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
// 引用计数加 1
CFRetain(fontRef);
// 引用计数减 1
CFRelease(fontRef);
对于 CFRetain 和 CFRelease 两个方法,读者可以直观地认为,这与 Objective-C 对象的 retain 和 release 方法等价。
所以对于底层 Core Foundation 对象,我们只需要延续以前手工管理引用计数的办法即可。
除此之外,还有另外一个问题需要解决。在 ARC 下,我们有时需要将一个 Core Foundation 对象转换成一个 Objective-C 对象,这个时候我们需要告诉编译器,转换过程中的引用计数需要做如何的调整。这就引入了bridge相关的关键字,以下是这些关键字的说明:
__bridge: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
__bridge_retained:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
__bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。
因为自己也是刚学习完ios内存管理,没有很深刻的理解和体会,怕浅薄的理解误导大家,所以这篇文章更像是一篇笔记整理文。
下附文章摘抄地址:
iOS里的内存管理
理解 iOS 的内存管理
iOS基础:深入内存管理-从所有权修饰符开始
iOS基础:深入内存管理-让人头疼的autorelease
如有侵权,请联系删除