美文网首页
五、内存管理

五、内存管理

作者: 那样风采 | 来源:发表于2018-11-07 17:04 被阅读4次

引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。不管是Objective-C语言还是Swift语言,其内存管理方式都是基于引用计数的。

1、引用计数

1.1 介绍

引用计数可以有效地管理对象生命周期:

  • 创建新对象时,引用计数为1
  • 有一个新指针指向这个对象时,引用计数加1
  • 某个指针不再指向这个对象时,引用计数减1
  • 对象引用计数变为0时,将对象销毁,回收内存
    由于引用计数简单有效,除了Objective-C语言外,微软的COM(Component Object Model)、C++11(C++11提供了基于引用计数的智能指针share_prt)等语言也提供了基于引用计数的内存管理方式。为了运行示例,我们需要启动手工管理引用计数的模式,因为现在默认的工程都开启了自动的引用计数,文件属性加上-fno-objc-arc的编译参数:


    image.png
    image.png

    当retainCount为0时,对象会被销毁,再向对象发送消息,程序会报错,因为这个时候指针指向的内存已经被销毁了,成为了野指针。
    引用计数类似于Linux文件系统里面的硬链接:

  • 用ln命令可以创建一个硬链接(类似retain)
  • 删除一个文件时,系统会检查文件的link count,大于1,不回收文件所占用的磁盘区域
  • 最后一次删除,link count为1,系统才会执行真正的删除操作,把文件所占用的磁盘区域标记成未使用

1.2 引用计数的作用

1.1中的示例,反映不出引用计数真正的用处,因为示例中的对象的生命周期只是在一个函数中,而在函数内使用一个临时的对象,通常用不上它的引用计数,只要函数返回前将该对象销毁即可。应用计数真正排上用场的场景是在面向对象的程序设计架构中:用于对象直接传递和共享数据。

image.png

假如对象A生成了一个属性对象M,需要调用对象B的某一个方法,将对象M作为参数传递过去。对象M的释放原则如下:

  • 对象A负责销毁M,使用M的对象B可能只是临时用一下对象M,也可能觉得对象M很重要,将它设置成自己的一个成员变量,那对象M的释放时机就成了一个难题!
  • 暴力做法,对象A传递M后,立马销毁M,对象B需要复制一份M2,对象B自己管理M2的生命周期。但是,这样会带来更多的内存申请、复制、释放的工作。太影响性能。
  • 对象B负责销毁M,对象B在用完后可以选择销毁M,这种做法强烈依赖于A、B两个对象的配合,代码维护者需要明确记住这种变成约定。对象M的申请和释放代码分散在不同的对象中,增加了管理的成本。如果遇到更复杂的情况,对象B又传递M给了对象C,实际的情况会更加复杂。

总结:

引用计数可以很好的解决上述问题,在参数M的传递过程中,哪些对象需要使用这个对象,就把它的引用计数+1,使用完了后引用计数-1。
所有对象都遵守这个规则的话,对象的生命周期管理就可以完全交给引用计数了。我们也可以很方便地享受到共享对象带来的好处。

1.3 释放对象

image.png
//引用计数为1时,再次释放
[object release]; 
//这时候引用计数为0,但是retainCount不会置0。在上图中,程序异常崩溃了
NSLog(@"Reference Count = %lu", (unsigned long)[object retainCount]);

当引用计数为0时,对象的内存会被回收,而我们向一个已经被回收的对象发了一个retainCount消息,它的输出结果应该是不确定的,如果该对象的所占的内存被复用了,那么就有可能造成程序异常崩溃。
当最后一次执行release,系统知道马上就要回收内存了,就没有必要将retainCount减少1了,因为不管减不减,该对象都肯定会被回收,它的所有的内存区域,包括retainCount值也变得没有意义。不将这个值从1变成0,可以减少一次内存的操作,加速对象的回收。
例如Linux文件系统举例,Linux文件系统下删除一个文件,也不是真正地在文件的磁盘区域进行抹除操作,而只是删除该文件的索引节点号。与引用计数的内存回收方式类似,即回收时只做标记,并不抹除相关的数据。

补充:

当对象的引用计数变成0的时候,系统会调用delloc方法去销毁对象。我们可以在delloc方法中去销毁之前引用对象的指针,以及取消已经订阅的KVO,通知等。

1、调用 -release :引用计数变为零
* 对象正在被销毁,生命周期即将结束.
* 不能再有新的 __weak 弱引用, 否则将指向 nil.
* 调用 [self dealloc]
2、父类 调用 -dealloc
* 继承关系中最底层的父类 在调用 -dealloc
* 如果是 MRC 代码 则会手动释放实例变量们(iVars)
* 继承关系中每一层的父类 都在调用 -dealloc
3、NSObject 调 -dealloc
* 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法
4、调用 object_dispose()
* 为 C++ 的实例变量们(iVars)调用 destructors
* 为 ARC 状态下的 实例变量们(iVars) 调用 -release
* 解除所有使用 runtime Associate方法关联的对象
* 解除所有 __weak 引用
* 调用 free()

1.4 循环引用及检测

引用计数这种管理内存的方式虽然很简单,但是有一个比较大的瑕疵,即它不能很好地解决循环引用问题。


image.png

当前对象A和对象B的引用计数都是1,如果想要将A和B销毁,必须置引用计数为0,在没有程序员介入的情况下,对象A要释放,其引用计数必须减一,这样其成员变量B的引用计数就会减一,但是对象A的引用计数要减一,必须让对象B销毁,即B的引用计数减一才可以,这样对象A和B都依赖于对方,都在等待对方去释放。

image.png

如上图所示,多个对象间,依次引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减1,即依次持有,形成一个环状,在真实的编程环境中,环越大就越难发现。

解决方案:

1、主动断开
主动断开循环引用依赖于程序员自己手工显示地控制,相当于回到“谁申请谁释放”的手动内存管理,同时也依赖于程序员的能力,发现循环引用并在合适的时机断开循环引用回收内存。
2、弱引用
弱引用,虽然持有对象,但并不增加引用计数。比如:delegate。

检测循环引用

Xcode的Instruments工具集: image.png 点击Profile后,Xcode会打开模拟器,并安装程序到模拟器,但是不会运行程序,然后在打开Instruments工具集: image.png
选择Leaks,然后点击Choose: image.png 这个时候检测面板会弹出,此时还没有开始检测,鼠标悬停在左上角红色按钮上时,显示:Start an immediate mode recoding。点击按钮开始检测并记录,这时程序会被显示运行: image.png

上图中,已经检测到了一次内存泄漏,选中这次内存泄漏,下面详情区域切到Cycles & Roots:


image.png

使用Instrument显示设备离线(device offline)解决方案:
关闭Instruments,然后Xcode依次进行:清理(shift+command+K)+编译(command+B)+运行(command+I)

2、使用ARC

非ARC的应用,迁移到ARC,有一些迁移成本,但是Xcode专门集成了迁移工具,成本已经非常小了,而且,为了兼容第三方的非ARC开源库,你也可以在工程中随意使用编译参数-fno-objc-arc,这个参数允许对部分文件关闭ARC:


image.png

虽然ARC是与IOS5一同推出的,但是由于ARC的实现机制是在编译期完成的,所以使用ARC之后应用仍然可以支持iOS4.3。稍微需要注意的是,如果要在ARC开启的情况下支持IOS4.3,需要将weak关键字换成__unsafe_unretained。

Core Foundation对象

    CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, "hello world", kCFStringEncodingUTF8);
//CF对象需要手动管理
    CFRetain(str);
    CFRelease(str);

在ARC下,我们有时需要将一个Core Foundation对象转换成一个OC对象:

  • ___bridge :只做类型转换,不修改相关对象的引用计数,原来的Core Foundation在不用时,需要调用CFRelease方法。
  • ___bridge_retained :类型转换后,将对象的引用计数加+1,原来的Core Foundation对象在不用时,需要调用CFRelease方法。
  • ___bridge_transfer :类型转换后,将对象的引用计数交给ARC管理,原来的Core Foundation对象在不用时,不需要调用CFRelease方法。

相关文章

  • OC - OC的内存管理机制

    导读 一、为什么要进行内存管理 二、内存管理机制 三、内存管理原则 四、MRC手动内存管理 五、ARC自动内存管理...

  • 《Effective Objective-C 2.0》读书笔记(

    第五章 内存管理 第29条:理解引用计数 OC 中有手动内存管理(MRC) 自动内存管理(ARC)手动内存管理需要...

  • 内存管理

    目录一、内存分区 1、RAM和ROM 2、内存的五大分区二、内存管理 1、OC内存管理是指什么?OC内存管理的本质...

  • 五、内存管理

    引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。不管是Objective-C语...

  • JVM总结

    1. JVM五大组成部分: PC栈本地方法栈堆方法区 2. GC: 2.1 内存管理机制 显示内存管理隐式内存管理...

  • Android App内存优化

    目录: 一、内存优化的意义 二、Android 内存管理 三、内存问题分类 四、内存优化辅助工具 五、内存优化建议...

  • iOS开发 内存管理

    一、内存五大区 二、内存管理方案 iOS操作系统是针对不同场景,会提供不同的内存管理方案, TaggedPoint...

  • Effective Objective-C 2.0笔记(四)

    第五章 内存管理 第29条:理解引用计数 OC使用引用计数管理内存,引用计数机制通过递增递减的计数器来管理内存。对...

  • iOS内存管理详解

    目录 block内存管理 autorelease内存管理 weak对象内存管理 NSString内存管理 new、...

  • Effective Objective-C 2.0 读书笔记五

    第五章 内存管理 内存管理对一门语言来说异常的重要,掌握一门语言的内存管理是很必要的。 29. 理解引用计数 OC...

网友评论

      本文标题:五、内存管理

      本文链接:https://www.haomeiwen.com/subject/prcltqtx.html