iOS 内存管理:从 MRC 到 ARC

笔者做为一个刚接触 iOS 不久的新手小白,被什么时候须要使用 weak 弱引用折磨了许久,看了许多文章和书之后总结了 iOS 内存管理的一些相关知识。本文讲解比较浅显,不涉及源码实现,若想深刻了解 ARC 建议阅读《Objective-C 高级编程》这本书。

程序内存分布

首先,咱们从 C 语言开始简单了解程序内存分布。一个由 C 语言编写的程序内存主要由如下5个部分组成:程序员

其中代码段、数据段、BSS 段在编译时由编译器分配空间,而堆和栈是在程序运行时系统分配的空间。编程

栈是用于存放本地变量,局部变量,函数参数值的内存区域。程序在运行时,操做系统会经过压栈和弹栈的方式来自动的分配和释放,不须要咱们手动干预。堆是用于存放除了栈里的东西以外全部其余东西的内存区域,通常由程序员手动分配和释放,若程序员不释放,程序结束时可能由操做系统回收。在 iOS 中,全部的值类型是放在栈空间的,内存分配和回收不须要咱们关心,系统会处理。而全部继承了 NSObject 的对象都存放在堆里,须要咱们本身负责分配和释放。安全

引用计数器

Object-C 和 Swift 为咱们提供了基于引用计数的内存管理方式。从字面上理解,引用计数器表明“对象被引用的次数”,也能够理解为有多少人正在使用这个对象。每一个对象都有本身的引用计数器,系统是根据对象的引用计数器来判断何时须要回收其所占用的内存空间的。网络

  • 任何一个对象,刚建立的时候引用计数为1(alloc/new/copy)
  • 当且仅当对象的引用计数为0时,系统才会回收这个对象(dealloc)

所以,当咱们须要持有对象时,为了保证对象的存在,须要使引用计数器+1;当咱们再也不须要持有对象时,须要使引用计数器-1,以便系统能够回收其内存空间。函数

MRC 手动管理引用计数

在 OC 中,NSObject 提供了 alloc 类方法,retain/release/dealloc实例方法用于内存管理,MRC 就是经过手动调用这些方法来实现对象引用计数的增长和减小。手动管理内存须要遵照如下几个原则:oop

  • 本身生成的对象,本身所持有

当程序调用方法名以 alloc/new/copy/mutableCopy 开头的方法来建立对象时,意味着本身生成的对象只有本身持有,该对象的引用计数+1。spa

  • 非本身生成的对象,本身也能持有

当程序调用对象的 retain 方法时,意味着程序持有了非本身生成的对象,该对象的引用计数+1。操作系统

  • 再也不须要本身持有的对象时释放

本身持有的对象,一旦再也不须要,不管是不是本身生成的,持有者都有义务释放该对象。程序经过调用对象的 release 方法释放该对象,引用计数-1。指针

可是有时候咱们不知道到底何时能够将对象释放,例如做为函数返回值返回时。为了解决这个问题,OC 提供了 autorelease 方法。code

autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,会将对象放到一个自动释放池 autorelease pool 中,而对象自己的引用计数并不增长,相似于 C 语言中局部变量的特性。在程序主循环的 RunLoop 或在其余程序可运行的地方,会对 release pool 对象进行生成、持有和废弃处理。当自动释放池被销毁时,会对池子里面的全部对象作一次release操做,从而达到管理引用计数的目的。

  • 非本身持有的对象不能释放

对于既不是以 alloc/new/copy/mutableCopy 开头的方法生成并持有的对象也不是用 retain 方法持有的对象,绝对不能使用 release 方法释放对象,不然就会形成程序崩溃。

ARC 自动管理引用计数

管理引用计数的本质部分在 ARC 中其实并无发生改变,ARC 只是会自动地帮咱们处理引用计数。ARC 是编译器特性,而不是运行时特性,能够对每一个文件选择使用或不使用 ARC,若使用 ARC,编译器在编译时会帮咱们自动的插入上一节提到的相关代码,包括retain/release/copy/autorelease/autoreleasepool等等,经过自动生成的代码去自动释放或保持对象。

ARC 中,咱们再也不使用 retain/release/autorelease 方法来管理内存,而是为每一个变量添加全部权修饰符,系统经过变量的全部权修饰符判断如何处理引用计数。

strong

__strong 修饰符是 id 类型和全部对象类型默认的全部权修饰符,表示对对象的强引用,对应属性声明中的 strong/retain/copy 属性。在 ARC 中,给被 __strong 修饰符修饰的变量赋值便可达成对对象的持有,而该变量在超出其变量做用域后被废弃,随着强引用的失效,其引用的对象也会随之释放,从而达到管理引用计数的目的。

weak

__weak 修饰符表示对对象的弱引用,对应属性声明中的 weak 属性。在 ARC 中,弱引用不能持有对象实例,因此在超出其变量做用域时,对象即被释放,能够用来避免出现循环引用的问题。在持有某对象的弱引用时,若该对象被废弃,则此弱引用会自动失效,且处于赋值 nil 的状态。

unsafe_unretained

__unsafe_unretained 修饰符是不安全的全部权修饰符,对应属性声明中的 assign/unsafe_unretained 属性。在 ARC 中,被 __unsafe_unretained 修饰的变量不属于编译器的内存管理对象。

autorelease

ARC 中虽然不能调用 autorelease,可是能够经过将对象赋值给附加了 __autorelease 修饰符的变量来替代调用 autorelease 方法,将对象注册到 autorelease pool。

空指针与野指针

空指针是指没有存储任何内存地址的指针,或是被赋值为 nil 的指针。在 OC 中,经过空指针访问空对象或是给空指针发消息都是安全的,不会产生异常或引发崩溃。

而野指针是不安全的。一个已经被释放的对象,叫作僵尸对象,指向僵尸对象的指针被称为野指针,野指针的存在是十分危险的。若是咱们经过野指针去访问僵尸对象,在原对象空间尚未从新分配出去以前,是不会出现问题的,可是若是空间已经被从新分配(有很大的可能),那么就会引发程序崩溃。所以对于野指针,咱们须要当心处理。

循环引用

引用计数式内存管理中必然会发生的问题,就是循环引用问题。若是两个对象 A 和 B 互相强引用,那么它们永远不会被释放,即便外界已经没有任何指针可以访问到它们了,它们依然存在,而且互相引用,没法被释放。循环引用容易发生内存泄漏,由于应当废弃的对象在超出其生存周期后继续存在。

解决循环引用问题主要有两个方法:

1. 主动断开循环引用

当咱们知道这里会产生循环引用时,在合理的位置主动断开环中的一个引用,打破循环,使得对象能够被回收。例如 GCD 和 YTKNetwork 网络请求等,因为持有 block 形成了循环引用,在运行结束后会采起这种方法在 block 执行完成后,主动释放对于 block 的持有,将其赋值为 nil,主动打破循环引用,避免内存泄漏。

// YTKNetwork 中执行完成后对于 block 的处理
- (void)clearCompletionBlock {
    self.successCompletionBlock = nil;
    self.failureCompletionBlock = nil;
}复制代码

不过,主动断开循环引用这种操做依赖于程序员本身手工显式地控制,至关于回到了之前 “谁申请谁释放” 的 MRC 内存管理年代,它依赖于程序员本身有能力发现循环引用而且知道在什么时机断开循环引用回收内存,因此这种解决方法并不经常使用,更常见的办法是使用弱引用的办法。

2. 使用弱引用

弱引用并不持有对象,也就不会增长引用计数,这样就避免了循环引用的产生。在 iOS 开发中,最多见的弱引用就是 delegate。举个例子来讲,ViewController 持有某个 View, 同时 View 的一些点击事件须要经过 delegate 的方式交给 VC 来处理。这个时候,View 的 delegate 成员变量一般是一个弱引用,以免相互引用对方形成循环应用的问题。

除了 delegate,还有一个弱引用最常出现的地方是 block。因为 block 会捕获 block 中使用的变量,稍一不注意就有可能会出现循环引用的问题。例如,ViewController 持有 block,同时 block 中的代码又用到了 self,那么这个时候就会形成循环引用。为了不循环引用就须要使用弱引用 weakself,最多见的写法是 weak-strong-dance。

__weak typeof(self) weakself = self;
self.completionHandler = ^(void){
    __strong typeof(weakself) strongself = weakself;
    // do something with strongself
}复制代码

须要注意的是,weak-strong-dance 有时会致使 block 中的代码并无执行,由于有可能在须要执行 block 中的代码时 self 已经被释放,弱引用 weakself 被置为 nil。而且,并非全部的 block 中使用 self 都会形成循环引用,像上文提到的,一些类会在执行完成后主动释放,所以咱们在使用前须要特别注意是否会引发循环引用,再来使用 weak-strong-dance。

相关文章
相关标签/搜索