Objective-C 内存管理 | 青训营笔记

150 阅读4分钟

OC 内存管理

这是我参与「第四届青训营 -IOS场」笔记创作活动的第1篇笔记

背景知识

Stack 栈 vs. Heap 堆

  • Stack
    • Fist in last out
    • 空间向下延伸, 向低地址扩展. 最底部位栈顶, 由栈指针(stack pointer) 指向,做标记.
    • 用于存储参数、局部变量、函数调用返回地址.
      • 局部指针变量存储于栈中, 而局部指针所指向的空间位于堆(heap)里.
  • Heap
    • 空间向上延伸, 向低地址扩展.
    • 空间一般由手动分配, 用于代码运行时声明的空间(malloc).
    • 存储数组对象,线程共享。

C 和 C++内存管理

  • C: 用 malloc & free 动态分配/释放内存空间. 需要手动计算类型大小, 管理内存失败返回 0.
  • C++: 增加了 new & delete 用于调用构造函数和析构函数用于实例对象的初始化与清理. 可自动计算类型大小, 管理内存失败返回异常.

IOS 内存管理

OC 内存管理

  • 非 OC 对象 (如基本类型变量 int/float/char...) 位于栈内存.
  • OC 对象(如 NSObject 对象及其子类) 位于堆内存, 需要内存管理.
  • 常见问题, 为什么需要管理内存:
    • 多个对象之间相互强引用 strong reference,构成循环, 其引用计数 reference counter >= 0, 导致不能释放, 无法被系统回收.
    • 单个程序内存占用过多, 造成Out Of Memory (OOM) crash.
    • 指针所指向的空间被提前释放,导致野指针 wild pointer, 由于野指针所指向的内存位置的内容是不确定的,因此造成crash.

MRC: Manual Reference Counting

  • 在OC中,对象是通过引用计数 reference countering 来进行内存管理. 当一个对象被持有 retain (被一个指针变量所指向), 引用计数就会递增。反之, 如果某个持有(指针) 被释放 release, 指针计数就会递减。当一个对象的指针计数为0时, 对象及其所占内存空间就会被系统回收。
  • alloc, new, copy, or mutableCopy 开头的方法创建的对象,引用计数 = 1.
  • 对对象发送 retain 消息: 引用计数 += 1
  • 对对象发送release 消息: 引用计数 -= 1
  • 如果对象的引用计数 == 0, 即将销毁前, 系统会对其发送 dealloc 消息.
  • Autorelease vs. release
    • release: 马上释放对对象的强引用
    • autorelease: 延迟释放对象
      • Autorelease-Pool: 对象会放到自动释放池, 统一释放, 在部分场景下可降低内存峰值.

ARC: automatic Reference Counting

  • 编译器会自动加入内存管理代码 (retain/release code)
  • 不需要通过 retain/release 来手动维护引用计数, 只用专注于对象的初始化构建.
  • ARC 将在对象强引用个数=0 时才销毁对象, 而不是引用计数=0时.
  • ARC 默认所有对象变量的指针都为强指针。但也可显示声明弱指针.

@Property 有关内存管理的修饰符

参考资料: Objective-C属性(property)的特性(attribute)

  • Strong: (default, Only for OC object) 强持有关系
    • 属性对一个对象有强引用/持有
    • 在强持有被释放前, 对象将一直被存储在堆内存中.
  • Weak: 弱持有关系
    • 属性对一个对象有弱引用, 但不会对其主张所有权(不会增加 引用计数)
    • 即使本属性的引用被解除, 如果还存在其他对该对象的强持有, 对象将一直被存储在堆内存中.
    • 如果一个对象被销毁, 那么所有引用此对象的weak 属性都将变成 nil.
  • Copy:
    • 在对属性赋值前,先回对传入对象进行 copy 操作, 再对copy 的结果主张所有权.
    • 实现方式是在setter内部调用copy方法: _val = [obj copy]
    • Note: 用 copy 修饰 mutable 对象(如NSMutableString), 赋值时将会出现警告, 因为setter中 copy的操作会吧 mutalbe对象 转变成 immutable对象.
  • Assign:
    • weak 类似, 唯一区别是, 当其引用的对象被销毁时, 那么所有引用此对象的 assign 属性都将变成野指针.
    • 常用语修饰基本类型(int/char/float...)

浅拷贝 vs. 深拷贝

  • 浅拷贝 Shallow copy, copy-by-reference. 指针拷贝, 拷贝后的指针指向原被拷贝数据内存区域.
  • 深拷贝 Deep copy, copy-by-value. 内容拷贝, 拷贝后的指针指向全新的内存区域, 其中的内容与被拷贝数据一致.

可变对象与不可变对象的复制

Immutable Obj (Copy Type)Immutable Obj (Result Type)Mutable Obj (Copy Type)Mutable Obj (Result Type)
copyShallow CopyImmutable ObjDeep CopyImmutable Obj
mutableCopyDeep CopyMutable ObjDeep CopyMutable Obj
  • Note: NSMutableString 是NSString的子类.

copy vs. strong

  • 对于不可变对象(copy 或 strong),属性赋值时都是浅拷贝
  • 对于可变对象,copy 修饰的属性赋值时是深拷贝, strong修饰的属性赋值时是浅拷贝.

Q: 为什么不可变对象一般用 copy 修饰而不是 strong?

  • 防止传入可变对象
  • 在copy 的修饰下, 不论传入对象是 mutable 还是 immutable, 传入的结果都是深拷贝的副本, 且不会被外部修改所影响.