iOS 内存管理

内存分配

  • 栈 stack
    • 由系统管理,分配和释放
    • 存储局部变量,保存函数现场
    • 连续的内存地址,由高向低分配,不会产生碎片
    • 效率高。栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
    • 类似于数据结构中的栈,先进后出

    每一个方法执行的时候都会向栈区申请内存,这部分内存随着方法的结束而释放,由系统自动分配。栈区的大小是事先规定好的(2M / 1M),如果申请的空间超过剩余可用空间就会发生 stackoverflow。

  • 堆 heap
    • 开发者手动管理 alloc release
    • 不连续的内存地址,由低向高分配,容易产生碎片
    • 分配方式类似链表,先进先出
    • 效率不如栈。计算机底层并没有对堆的支持,堆是有 C/C++ 函数库提供的,加上碎片问题,导致堆的效率比栈低。

    系统会有个记录空闲地址的链表,当系统收到内存申请的时候,就去遍历该链表,寻找第一个空间大于申请空间的堆结点,然后将该结点删除,并将该结点的空间分配给程序。如果分配的空间有多余,系统将会把多余的空间重新放回链表中。虽然程序结束后,所以的数据空间都会被系统回收,但是精确的申请与释放内存是我们必备的素质。

  • 全局区 / 静态区
    • 存储全局变量、静态变量
    • bbs:未初始化;data:初始化的
  • 常量区
    • 常量字符串
  • 代码区
    • 存储 App 二进制代码

ARC 与 MRC

对象操作

  • alloc/new/copy/mutableCopy 生成并持有对象,retainCount: +1
  • retain 持有对象,retainCount: +1
  • release 释放对象,retainCount: -1
  • dealloc 废弃对象

内存管理基本规则

  • 生成并持有对象
  • 持有非自己生成的对象
  • 不再需要自己持有对象的时候释放
  • 非自己持有的对象无法释放

ARC 原理

ARC 是苹果在 LLVM 3.0 开始引用一种内存管理机制,会根据对象的引用计数自动监控对象的生命周期,实现方式是在编译时期自动在已有的代码中插入合适的内存管理代码以及在 Runtime 做一些优化。

所有权修饰符

ARC 规定每个对象前必须加上所有权修饰符:

  • __strong
    默认的所有权修饰符,表示对对象的强引用,持有强引用的对象在超过其作用域时被废弃
// ARC 无效
{
    id obj = [[NSObject alloc] init];
    [obj release];
}

// ARC 有效
{
    id __strong obj = [[NSObject alloc] init];
}
  • __weak
    表示对对象的弱引用,主要用于避免循环引用,如果对象没有强引用了,弱引用会被置为 nil
  • __unsafe_unretained
    不安全所有权修饰符,同 __weak 作用一样,只是在对象没有强引用的时候不会被置为 nil,该指针就变成了悬垂指针
  • __autorelesing
    用于替代 autorelease 方法
属性修饰符

赋值性

  • strong 强引用,先保留新值,再释放旧值,最后赋值
- (void)setValue:(id)newValue {
    [newValue retain];
    [_value release];
    _value = newValue;
}
  • retain 在 ARC 下与 strong 类似,MRC 下修饰 block 时,strong 会把 block 拷贝到堆区,retain 不会
  • weak 弱引用,既不保留新值也不释放旧值,当对象被释放时,自动指向 nil
  • unsafe_unretained 与 weak 一样,只是当对象被释放时,不会自动指向 nil
  • assign 赋值操作,通常用于基本数值类型,不涉及应用计数,默认属性
  • copy 类似于 strong,不过在赋值的时候进行的是 copy 而不是 retain 操作,一般用于修饰不可变对象( NSString )和 block

读写性

  • readonly 只读特性,只生成 getter 方法
  • readwrite 可读可写特性,生成 settergetter 方法,默认属性

原子性

  • nonatomic 非原子特性,不加同步,多线程访问时效率高,但是线程不安全,settergetter 不是原子操作
  • atomic 原子特性,同步操作,多线程安全(不是绝对,因为),settergetter 是原子操作,默认属性

指定方法名

  • getter = ,setter =
相关问题

NSString 为什么要用 copy 修饰?
NSMutableString 是 NSString 的子类,所以可以把一个 NSMutableString 赋值给 NSString,这样就不能保证 NSString 不可变性了,所以最好使用 copy 修饰,这样赋值的时候就会先拷贝新值然后赋值,不管可变不可变都会变成不可变字符串。

block 为什么要用 copy 修饰?
在 MRC 环境下时,block 默认是放在栈里面的,所以要用 copy 修饰,将其拷贝到堆区,避免释放。而在 ARC 环境下,用 storng 修饰,系统也会自动将其拷贝到堆区,所以用 strong 和 copy 修饰都是可行的,但是苹果建议我们用 copy 修饰,这样能显式地指明内存行为,官方文档描述如下:

Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.

atomic 为什么不是一定线程安全的?
atomic 只是表明该属性的 settergetter 是线程安全的。

ARC 优点

  • 减少工作量,无需键入 retain 或者 release 代码
  • 降低程序崩溃、内存泄漏等风险
  • 编译器完全清楚目标对象,并能立即释放那些不再被使用的对象,这样应用程序就具有可预测性,且能流畅运行,速度也将大幅提升

不要使用 retainCount

retainCount 在实际的 release 环境中是没有什么作用的,在 debug 的时候,retainCount 返回的数值也不一定准确,因为系统会对 retainCount 进行优化,比如当一个对象的 retainCount 为 1 的时候,再对它进行 release 操作,系统可能只是标记该内存可回收,没必要再继续一次 -1 的数值操作,这个时候返回的 retainCount 就不会为 0。而且对于加入自动释放池的对象,对其发送 retainCount 消息是一件很危险的事情,因为无法确定其是否被释放,或者内存是否被复用,如果被其他对象复用,返回的 retainCount 必然是有问题的。

循环引用

  • 主动释放
  • 弱引用
    • weak 可以为空
    • unowned 不能为空

相关阅读

  • 从一道网易面试题浅谈OC线程安全
  • 从一道网易面试题浅谈 Tagged Pointer
  • 深入理解Tagged Pointer

你可能感兴趣的:(iOS 内存管理)