请稍侯

iOS内存管理机制

19 October 2015

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 方法。

以上参考:

iOS内存管理机制解析

理解iOS的内存管理

Objective-C中的内存分配

10个Objective-C基础面试题,iOS面试必备