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

Apple提供了两种内存管理方式:
- MRR(Manual retain-release), 手动保留释放。通常在运行时环境中使用。
- ARC(Automatic Reference Counting), 自动引用计数。在编译时使用。使用ARC的话,相当于将内存的管理下放到系统,我们只需关心对象的实现,而无需关心对象的释放与否的问题。(基本情况下不用care)。
内存管理原则
- 你持有自己所拥有的对象:通过使用
alloc
,new
,copy
或者mutableCopy
等方法命名的方法创建的对象,像stringWithFormat:
等方法创建的对象并不能被持有 - 使用
retain
来获取对象的拥有关系:retain
使用场景有两个: 1)在访问器的方法实现或者是init
方法中,获取想要的对象,并将其作为属性值 2)避免其他操作对该对象的造成的影响 - 在不使用该对象之后,必须舍弃掉该对象:发送
release
或者autorelease
的消息给该对象 - 不能释放非自己持有的对象!
release
和autorelease
发送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一个对象会创建该对象的强引用。
循环引用的解决

循环引用的一种解决方式是使用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
,mutableCopy
和new
打头的方法 - 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
:
- 比方说像命令行工具等不基于UI framework的程序
- 在自己创建的loop中创建了很多临时对象
- 生成辅助线程:这种情况的话必须在线程执行时创建自己的
autoreleasepool
block, 否则会造成内存泄漏
0x07 Other
- 一般情况下,
alloc
和release
配套使用