1、OC 基本内存管理模型
1.1 自动垃圾收集
- 在 OC 2.0 中,有一种称为垃圾收集的内存管理形式。通过垃圾收集,系统能够自动监测对象是否拥有其他的对象,当程序执行需要空间的时候,不再被引用的对象会自动释放。iOS 运行环境并不支持垃圾收集,在这个平台开发程序时并没有这方面的选项。在 OS X 10.8 中垃圾收集已不再推荐使用。
1.2 自动释放池
自动释放池(autoreleasepool)的机制是它使得应用在创建新对象时,系统能够有效的管理应用所使用的内存。自动释放池可以追踪需要延时一些时间释放的对象。
OC 对象只需要发送一条 autorelease 消息,就会把这个对象添加到最近的自动释放池中。autorelease 实际上只是把对 release 的调用延迟了,对于每一次 autorelease,系统只是把该对象放入了最近的 autoreleasepool 中。当执行到 autoreleasepool 块的末尾时,系统会释放自动释放池,这将影响到所有发送过 autorelease 消息并添加到自动释放池中的对象,系统会对池中的每个对象发送 release 消息,当这些对象的引用计数减到 0 时,会发出 dealloc 消息,并且他们的内存会被释放。
-
自动释放池并不包含实际的对象,而是只包含对象的引用。
-
1) 作用:
- 自动释放对象的。
- 所有 "autorelease" 的对象,在出了作用域之后,会被自动添加到 "最近创建的" 自动释放池中。
- 在自动释放池被销毁或者耗尽的时候,会向池中所有对象发送 release 消息,释放池中对象。
- 自动释放池,在 ARC & MRC 程序中,同样有效。
-
2) 创建销毁时间:
- 创建:运行循环检测到事件后,就会创建自动释放池。
-
销毁:一次完整的运行循环结束之前,会销毁。
![OCRam1]()
-
3) 自动释放池的 创建:
-
4) 自动释放池的 使用:
-
MRC
@autoreleasepool {
// 该 stu 对象不需要再 release 了,给对象添加延迟释放的标记,autorelease 作用就是延迟释放
Student *stu = [[[Student alloc] init] autorelease];
}
-
ARC
@autoreleasepool {
// 该 stu 对象不需要再 release 了,给对象添加延迟释放的标记,在 ARC 中系统会自动添加 autorelease
Student *stu = [[Student alloc] init];
}
-
5) 自动释放池的 疑问:
问题:在 iPhone 项目中,main() 中有一个默认的 autoreleasepool,程序开始时创建,程序退出时销毁,按照对 autoreleasepool 的理解,岂不是 autoreleasepool 里的所有对象在程序退出时才 release,这样跟内存泄漏有什么区别?
答案:对于每一个 Runloop(运行循环)系统都会隐式的创建一个 autoreleasepool,并把创建好的 pool 放在栈顶,所有的 pool 会构成一个栈式结构。在每一个 Runloop 结束时,当前栈顶的 pool 会被销毁,这样这个 pool 里的每个对象会被执行 release 操作。
-
6) 自动释放池的 注意:
- 在 ARC 下不能使用 [[NSAutoreleasePool alloc] init],而应当使用 @autoreleasepool。
- 不要把大量循环操作放在同一个自动释放池中,这样会造成内存峰值的上升。
- 尽量避免大内存使用该方法,对于这种延迟释放机制,还是尽量少用,而是使用 release 操作。
- SDK 中一般利用静态方法(类方法)创建并返回的对象都是已经 autorelease 的,不需要再进行 release 操作。
- 如果使用 MRC 开发,所有返回 instancetype 的类方法,返回的对象都是 autorelease 的。
1.3 MRC 手动引用计数
1.4 ARC 自动引用计数
1.5 ARC 与 MRC 编译环境的设置
-
1) 对整个工程做修改:
-
2) 对部分文件做修改(混编):
-
在使用 MRC 项目中,用 “使用 ARC” 的类库时,在 TARGETS => Build Phases => Compile Sources 中对类库的所有 .m 文件添加 “-fobjc-arc”
![OCRam3]()
-
在使用 ARC 项目中,用 “使用 MRC” 的类库时,在 TARGETS => Build Phases => Compile Sources 中对类库的所有 .m 文件添加 “-fno-objc-arc”
![OCRam4]()
2、MRC 中 assign 的使用
// Person.h
+ (instancetype)person;
// Person.m
+ (instancetype)person {
// autorelease 谁申请,谁释放,作用就是延迟释放,给对象添加延迟释放的标记
Person *p = [[[Person alloc] init] autorelease];
return p;
}
// ViewController.m
// 声明 assign 对象
@property (nonatomic, assign) Person *p;
// alloc/init 初始化方式
/*
MRC 中,assign 修饰符号,对对象不做任何操作,只是简单的记录地址,对象出了作用域后才会被释放
*/
self.p = [[Person alloc] init];
// 类方法初始话方式
/*
MRC 中,所有返回 instancetype 的系统类方法,返回的对象都是 autorelease 的
*/
self.p = [Person person];
3、ARC 中 weak 的使用
// Person.h
+ (instancetype)person;
// Person.m
+ (instancetype)person {
// 自动添加 autorelease,作用就是延迟释放,给对象添加延迟释放的标记
Person *p = [[Person alloc] init];
return p;
}
// ViewController.m
// 声明 weak 对象
@property (nonatomic, weak) Person *p;
// alloc/init 初始化方式
/*
ARC 中,如果给 weak 做 alloc/init 的初始话,Xcode 会提示,如果对象创建后没有被其他任何对象强引用,会被立即释放
*/
// 没有对 alloc 的对象做强引用,会被立即释放,使用 strong 则不会被立即释放
self.p = [[Person alloc] init];
// 对 alloc 的对象做强引用,不会被立即释放,出了作用于后会被释放
NSString *str = [[[Person alloc] init] description];
// 类方法初始话方式
/*
对于 Xcode 编译器而言,只要是类方法,就不会提示。结果之所以为 null,是因为 weak 创建后没有被其他任何对象强引用,
会被立即释放,跟 autorelease 没有关系
*/
// 没有对创建的对象做强引用,会被立即释放,使用 strong 则不会被立即释放
self.p = [Person person];
// 对创建的对象做强引用,不会被立即释放,出了作用于后会被释放
NSString *str = [[Person person] description];
-
weak 的内部实现原理
weak 变量在引用计数为 0 时,会被自动设置成 nil,这个特性是如何实现的?
在 Friday QA 上,有一期专门介绍 weak 的实现原理。https://mikeash.com/pyblog/friday-qa-2010-07-16-zeroing-weak-references-in-objective-c.html
-
简单来说,系统有一个全局的 CFMutableDictionary 实例,来保存每个对象的 weak 指针列表,因为每个对象可能有多个 weak 指针,所以这个实例的值是 CFMutableSet 类型。剩下我们要做的,就是在引用计数变成 0 的时候,去这个全局的字典里面,找到所有的 weak 指针,将其值设置成 nil。如何做到这一点呢?Friday QA 上介绍了一种类似 KVO 实现的方式。当对象存在 weak 指针时,我们可以将这个实例指向一个新创建的子类,然后修改这个子类的 release 方法,在 release 方法中,去从全局的 CFMutableDictionary 字典中找到所有的 weak 对象,并且设置成 nil。我摘抄了 Friday QA 上的实现的核心代码,如下:
Class subclass = objc_allocateClassPair(class, newNameC, 0);
Method release = class_getInstanceMethod(class, @selector(release));
Method dealloc = class_getInstanceMethod(class, @selector(dealloc));
class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));
class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));
objc_registerClassPair(subclass);
当然,这并不代表苹果官方是这么实现的,因为苹果的这部分代码并没有开源。《Objective-C高级编程》一书中介绍了 GNUStep 项目中的开源代码,思想也是类似的。所以我认为虽然实现细节会有差异,但是大致的实现思路应该差别不大。
4、内存飙升问题解决