iOS内存管理相关

1,236 阅读5分钟

0x00 堆栈

  • 堆(heap):由程序猿负责分配和管理,存储OC对象,比方说继承自NSObject的所有对象,这些对象都是引用类型。
  • 栈(stack):由系统负责管理。存储如int, float等数据类型,这些对象都是值类型。栈的效率会相对较高。

0x01 对象分配和释放

内存管理方式

图片来源https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html

Apple提供了两种内存管理方式:

  1. MRR(Manual retain-release), 手动保留释放。通常在运行时环境中使用。
  2. ARC(Automatic Reference Counting), 自动引用计数。在编译时使用。使用ARC的话,相当于将内存的管理下放到系统,我们只需关心对象的实现,而无需关心对象的释放与否的问题。(基本情况下不用care)。

内存管理原则

  1. 你持有自己所拥有的对象:通过使用alloc, new, copy或者mutableCopy等方法命名的方法创建的对象,像stringWithFormat:等方法创建的对象并不能被持有
  2. 使用retain来获取对象的拥有关系:retain使用场景有两个: 1)在访问器的方法实现或者是init方法中,获取想要的对象,并将其作为属性值 2)避免其他操作对该对象的造成的影响
  3. 在不使用该对象之后,必须舍弃掉该对象:发送release或者autorelease的消息给该对象
  4. 不能释放非自己持有的对象!

releaseautorelease

发送release消息的话,会马上将对象释放掉。如果想要延迟释放对象的话,可以使用autorelease.

通过引用返回的对象并不能被持有。

使用dealloc来舍弃对象的持有

这里需要注意的一点是不能直接调用其他对象的dealloc方法

不要再init或者dealloc方法中使用访问器方法

苹果在官方文档中提到,不要在初始化或者是释放方法中使用Accessor Methods(这可能是在MRC阶段的,苹果的内存管理文档是比较久之前,不过这是个人理解,有待验证)。所以在初始化的方法中,应该这样:

- (instancetype)init {
  self = [super init];
  if (self) {
    _count = [[NSNumber alloc] initWithInteger: 0];
  }
  return self;
}

Core Foundation

对于一般的OC对象,只需要使用ARC来进行计数实现内存管理即可。对于底层的Core Foundation来说,则需要调用CFRetain()CFRelease()这两个方法来增减引用计数。

OC对象和Core Foundation对象的转换通过bridge关键字进行转换。

  • __bridge: 只进行类型转换,不修改引用计数
  • __bridge_retained: 类型转换后引用计数加1
  • __bridge_transfer: 类型转换后将对象的引用计数交给ARC管理

weak

weak引用是一种non-owning的关系,所以并不会retain对象。

声明为weak的变量可以防止循环引用造成的内存问题,因为如果对象没有了强引用的话,就会被置为nil。这一点需要跟unsafe_unretained区分开来。unsafe_unretained的对象如果没有了强引用,并不会被置为nil。如果对象被回收或者销毁的话,该指针就会变为野指针。

retain一个对象会创建该对象的强引用。

循环引用的解决

图片来源:Advanced Memory Management Programming Guide

循环引用的一种解决方式是使用weak引用。如图中所示,Cocoa有一个约定,就是父对象对其子对象是强引用,子对象则是对父对象弱引用。用上面的例子来说的话,就是Page对象强引用了Paragraph, Paragraph弱引用了Page.

发送消息给已经释放的对象的话,程序会crash,这点需要跟发送消息给nil进行区分。OC是允许发送消息给nil的。

集合类型retain其包含的对象

如Array, Dictionary和Set等集合对象,retain其object,在移除对象或者集合对象被释放的时候才会将该object舍弃。

0x02 野指针错误

野指针导致的错误通常在Xcode中表现为:Thread 1: EXC_BAD_ACCESS(code=EXC_I386_GPFIT)错误。原因是访问了一块已经释放或者不属于你的内存。

0x03 Retained/Unretained return value

  • Retained return value: 表示调用者拥有这个返回值,需要负责释放。比方说alloc, copy, init, mutableCopynew打头的方法
  • Unretained return value: 调用者不拥有这个返回值。如[NSString stringWithFormat:]这样的方法

0x04 内存泄露

内存泄露有两种类型:

  • Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).
  • Abandoned memory: Memory still referenced by your application that has no useful purpose.

其中第一种可以使用Leak工具来检测,第二种使用Allocations来检测

0x05 Block的分配与释放

Block一开始是释放在栈中,但是在运行时会调用Block_copy方法将block对象复制到堆中,并且将引用计数加1,返回新的block指针(如果block对象已经存在于堆中的话,则引用计数加1)。而Block_release方法则是将引用计数减一,或者在计数为0时将对象释放摧毁。

#0x08 Autorelease Pool Block

@autoreleasepool {
  // Code
}

一般在下面三种情况下需要使用autoreleasepool

  1. 比方说像命令行工具等不基于UI framework的程序
  2. 在自己创建的loop中创建了很多临时对象
  3. 生成辅助线程:这种情况的话必须在线程执行时创建自己的autoreleasepool block, 否则会造成内存泄漏

0x07 Other

  1. 一般情况下,allocrelease配套使用

参考文章传送门

  1. Objective-C 中的内存分配