iOS内存管理机制
iOS内存管理机制
- Objective-C内存管理的对象
- 主要有两类,一类是值类型,比如int、float、struct等基本数据类型;
- 另一类是引用类型,也就是继承自NSObject类的所有的OC对象;
- 前一种值类型不需要我们管理,后一种引用类型是需要我们管理内存的;
- 值类型会被放入栈中,他们依次紧密排列,在内存中占有一块连续的内存空间,遵循先进后出的原则;
- 引用类型会被放到堆中,当给对象分配内存空间时,会随机的从内存当中开辟空间,对象与对象之间可能会留有不确定大小的空白空间,因此会产生很多内存碎片,需要我们管理;
- 栈内存与堆内存从性能上比较,栈内存要优于堆内存,这是因为栈遵循先进后出的原则,因此当数据量过大时,存入栈会明显的降低性能。因此,我们会把大量的数据存入堆中,然后栈中存放堆的地址,当需要调用数据时,就可以快速的通过栈内的地址找到堆中的数据。
- Objective-C管理内存的方式
- MRC(人工引用计数),手动管理内存;
- ARC(自动引用计数),自动管理内存;
- 与内存有关的修饰符
- 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相反。
- 引用计数
- 引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。
- 当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。
- 由于引用计数简单有效,除了 Objective-C 和 Swift 语言外,微软的 COM(Component Object Model )、C++11(C++11 提供了基于引用计数的智能指针 share_prt)等语言也提供了基于引用计数的内存管理方式。
- 对 Linux 文件系统比较了解的人可能发现,引用计数的这种管理方式类似于文件系统里面的硬链接。在 Linux 文件系统中,我们用 ln 命令可以创建一个硬链接(相当于我们这里的 retain),当删除一个文件时(相当于我们这里的 release),系统调用会检查文件的 link count 值,如果大于 1,则不会回收文件所占用的磁盘区域。直到最后一次删除前,系统发现 link count 值为 1,则系统才会执行直正的删除操作,把文件所占用的磁盘区域标记成未用。
- ARC
- ARC 背后的原理是依赖编译器的静态分析能力,通过在编译时找出合理的插入引用计数管理代码;
- Swift 语言,而该语言仍然使用 ARC 技术,作为其内存管理方式。
- ARC下还需要自己管理内存的情况:
- 遇到底层 Core Foundation 对象,需要手动管理引用计数;
- 过度使用 block 之后,可能需要手动处理对象的引用(强弱引用处理)
- 处理循环引用的几种方案
- 主动断开循环引用。例如:
Controller持有了网络请求对象; 网络请求对象持有了回调的block; 回调的 block 里面使用了 self,所以持有了 Controller
; 解决办法就是,在网络请求结束后,网络请求对象执行完 block 之后,主动释放对于block的持有,将其置为null,以便打破循环引用。 - 使用弱引用。弱引用虽然持有对象,但是并不增加引用计数,这样就避免了循环引用的产生。
- 主动断开循环引用。例如:
- 弱引用的实现原理
- 系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。
- 可以看出,弱引用的使用是有额外的开销的。虽然这个开销很小,但是如果一个地方我们肯定它不需要弱引用的特性,就不应该盲目使用弱引用。
- 使用Xcode检测循环引用
- Xcode 的 Instruments 工具集可以很方便的检测循环引用;
- Xcode 的菜单栏选择:Product -> Profile,然后选择 “Leaks”,再点击右下角的”Profile” 按钮开始检测;
- Instruments 中会用一条红色的条来表示一次内存泄漏的产生。
- 切换到 Leaks 这栏,点击”Cycles & Roots”,就可以看到以图形方式显示出来的循环引用。
- Core Foundation 对象的内存管理
- 底层的 Core Foundation 对象,在创建时大多以 XxxCreateWithXxx 这样的方式创建。
-
对于这些对象的引用计数的修改,要相应的使用 CFRetain 和 CFRelease 方法。例如:
// 创建一个 CFStringRef 对象 CFStringRef str= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8); // 创建一个 CTFontRef 对象 CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL); // 引用计数加 1 CFRetain(fontRef); // 引用计数减 1 CFRelease(fontRef);
-
在 ARC 下,我们有时需要将一个 Core Foundation 对象转换成一个 Objective-C 对象,这个时候我们需要告诉编译器,转换过程中的引用计数需要做如何的调整。这就引入了bridge相关的关键字,以下是这些关键字的说明:
__bridge
: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。__bridge_retained
: 类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。__bridge_transfer
: 类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。
以上参考: