Objective-C 内存管理

ObjC 是 C 语言的超集,所以内存需要管理分配和释放;苹果在新的 LLVM 中增加了自动内存管理机制,让编译器来实现内存管理

引用计数 & MRC

引用计数是 Cocoa 中(不在OC语言中)用来管理内存的核心思想,可以说 ObjectiveC 中的内存管理就是引用计数。通过对对象的引用情况进行统计,如果对象没有任何引用(计数为0),则将该对象销毁

引用计数规则

引用计数的管理原则是:

  • 自己生成的对象,自己持有
  • 非自己生成的对象,自己也可以持有
  • 不再持有对象时要释放
  • 非自己持有的对象不能释放

规则与实现

引用计数的规则都有对应的方法与之对应:

  • 生成并持有对象:alloc\new\copy\mutableCopy 方法与以上诉单词开头的方法
    • allocate \ newer \ copying \ mutableCopyed 等方法虽然以规定的单词开头,但是不会生成并持有对象;需要方法名符合驼峰命名的规则
  • 持有对象:retain 方法;不是自己生成的对象,理论上是无主的(没有任何变量持有);需要显示的去持有它,否则会被立刻销毁
  • 释放对象:release 方法:无论持有的对象是否是自己生成的,当不再需要继续持有时,必须将它释放;但是释放非自己持有的对象会导致奔溃;多次释放也会导致崩溃
  • 销毁对象:dealloc 方法:当一个对象的引用次数降为0时,它会被销毁;系统会自动调用 dealloc 方法

以上内存管理的相关方法都由 CoreFoundation 中的基类 NSObject 中实现,因此所有的 ObjC 对象都能使用,而非 ObjC 对象则不能使用;所以说内存管理是 Cocoa 中的功能,而不是语言层面的

实现

苹果维护了一个全局的散列表(引用计数表)来管理引用计数;其中表的键值是对象内存块地址的 hash 值;这样做的好处在于

  • 引用计数表中记录了各个对象的内存块地址,可以从表中追溯到具体的对象;如果出现 bug 导致对象内存块损坏,可以通过表找到具体内存块的地址
  • 全局引用计数表同时还方便利用工具检测内存是否泄漏

Autorelease Pool

通过引用计数,大多数情况下只要遵守规则就能很好的管理内存,但是有些特殊情况却不能很好的处理。例如:当一个对象作为函数返回值时,它创建完被一个自动变量持有,当函数返回时,自动变量被销毁,然后释放该待返回对象,此时它的引用计数为0,对象被销毁。针对这一种情况,Cocoa 提供了另一种机制:延迟释放

原理

AutoreleasePool 是 Cocoa 中的延迟释放具体实现,它的原理类似于 C 中的作用域,将需要延迟释放的对象添加到 pool 中,当 pool 被销毁时(类似于变量超出作用域),pool 中的所有对象会被调用 release 方法。AutoreleasePool 与 Runloop 相关,每个 Runloop 循环结束时才会被销毁,因此可以保证里面的对象生命周期超出当前作用域到当前 Runloop 结束

Autorelease Pool 实现

AutoreleasePool 本质上是在内部维护了一个数组,当将一个对象添加到 pool 时,就是将对象添加到内部数组。待 AutoreleasePool 对象被销毁时就遍历该数组,依次调用 release 方法

autorelease 方法

autorelease 方法的实质就是调用 NSAutoreleasePool 对象的 +(void)addObject: 方法

  • +(void)addObject: 类方法会获取当前的 NSAutoreleasePool 实例,并调用 -(void)addObject: 方法

NSAutoreleasePoolautorelease 方法被重载了,对 NSAutoreleasePool 实例调用 autorelease 方法会导致崩溃

ARC

ARC 全称自动引用计数,本质上还是通过引用计数管理内存,不同之处是通过编译器完成对对象的引用计数管理

所有权修饰符

ARC 中使用所有权修饰符表述引用计数的规则

  • __strong 默认修饰符,表示强引用;持有强引用的变量在超出其作用域时被废弃,随之强引用失效,引用的对象会被释放
    • 对 __strong 修饰的变量赋值就满足自己生成的对象自己持有;非自己生成的对象自己也能持有的规则
    • 通过销毁 __strong 修饰的变量满足不再持有的对象被释放的规则
  • __weak 弱引用,保证引用不会持有对象;主要用于解决循环引用的问题,弱引用修饰的变量指向对象被销毁后会指向 nil
  • __unsafe_unretained 修饰后的对象不属于编译器的内存管理对象,作用效果和 __weak 一样。但是它可能指向野指针,导致崩溃
  • __autoreleasing 表示会将变量注册到 Autorelease Pool

__strong | __weak | __autoreleasing 会保证将变量初始化为 nil

__weak 会在变量引用的对象被销毁后指向 nil,保证程序不会崩溃;而 __unsafe_unretained 则会指向野指针

在变量间的赋值过程中,必须保证所有权修饰符一致(编译器会自动转换)

__autoreleasing 使用场景

  • 返回生成但自己不持有的对象时(非 alloc\new\copy 等方法生成的),需要保证没有立刻被销毁
  • 访问 __weak 修饰的变量时必定会注册到 Pool;因为 __weak 修饰的变量在引用过程中随时可能被销毁,只有将它添加到 Pool 中,才能保证在当前作用域中不被释放
  • 使用(id 或对象的指针)指针时,默认会被加上 __autoreleasing,例如:NSError * __autoreleasing * error;插在中间说明是作用于对象而不是指针地址,防止指针指向的对象被销毁

属性关键字

  • assign -> __unsafe_unretained assign 主要用于数值类型,不是OC对象,存放在栈中,不需要使用编译器管理内存
  • copy -> __strong 会将对象复制后再赋值
  • retain -> __strong
  • strong -> __strong
  • unsafe_unretained -> __unsafe_unretained
  • weak -> __weak

ARC 实现

ARC 是由编译器进行内存管理,但是实质上 OC 运行时在其中也发挥了巨大的作用。

__strong

编译器会在编译器针对不同的所有权修饰符插入 retainrelease 代码;再配合运行时优化程序

  • 将成对的无用 retainrelease 删除
  • 直接调用底层 C API 管理引用计数

__weak

__weak 不需要对对象引用,理论上来说不需要处理,但是它需要实现另外两个功能:

  • 在指向的对象被销毁后将变量指向 nil
  • 在引用 __weak 声明的变量时需要将变量添加到 autoreleasepool

针对上诉需求,系统维护了一个全局散列表,将所有 __weak 指向对象的地址作为 key,变量的地址作为 value;如果一个对象同时赋值给多个 weak 变量,则会用同一个键值注册多个 value (保存在数组中)

当对象被销毁后,操作如下

  1. 从 weak 表中获取销毁对象的地址为键值的所有地址记录
  2. 将包含在地址记录中的所有变量赋值为 nil
  3. 从 weak 表中删除该记录
  4. 从引用计数表中删除销毁对象的地址为键值的记录

因为对象被销毁后,会将 nil 赋值给该变量,因此使用大量 __weak 的变量会导致 CPU 资源消耗过高
同时如果多次使用 __weak 声明的变量,每次使用都会使得该变量被注册到 autorelease pool 中。这种情形可以将变量暂时赋值给 __strong 修饰的变量再使用,可以保证只会被注册到 autorelease pool 一次。每使用一次__weak对象,运行时系统都会将其指向的原始对象先 retain,之后保存到自动释放池中

__autoreleasing

__autoreleasing 等价于 MRC 环境下的 autorelease 方法;@autoreleasepool 等价于 MRC 环境下的 NSAutoreleasePool

ARC 与 C

对象型变量不能加到 C struct\union 中,因为 ARC 是通过编译器管理内存的,编译器必须知道并且管理对象的声明周期;但是 C 结构体的生命周期是不可知的

将对象强制转为 void * 指针或声明__unsafe_unretained不使用编译器管理内存之后可以加到 C 结构体中

OC 中的 id 类型与 C 中的万能指针可以通过 (__bridge) 互换:void *p = (__bridge void *)obj & id obj = (__bridge id)p; 但是很容易因为野指针崩溃

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2017-2021 HonQi

请我喝杯咖啡吧~