在使用面向对象的编程语言进行开发的过程中大都涉及到内存管理相关的问题;JAVA、C#等语言采用GC(垃圾回收)机制来管理内存的使用;而最早从事iOS开发的工程师则经历过MRC(手动管理)内存的阶段,后期apple推出了ARC(自动引用计数)的方式来简化内存的管理;那么ARC究竟是什么呢?ARC是如何进行内存管理的呢?
自动引用计数(ARC)
- 创建一个对象就是在内存中开辟了一块空间来存储对象的属性和行为,对象都有自己的生命周期,系统如何判断对象的生命周期完毕后就对它进行回收呢?
- iOS系统采用的是引用计数,在开辟的内存区域中存在一个NSInteger类型的变量,对象一旦创建它的值就为1,(通常情况下)有强引用指向它的值就会+1,强引用置为nil,它的值就会-1(retain消息会使得引用计数+1,release消息会使得引用计数-1);在一次事件循环结束后如果对象的引用计数为0则系统就会回收该对象;那么一次的事件循环还发生了什么呢?
- 首先要介绍自动释放池(autoReleasePool):它的实质是一个NSMutableArray,一次的事件循环都会创建一个自动释放池,事件循环中产生的对象会被依次加入到autoReleasePool中,事件循环结束后自动释放池会一次向存储的对象发送release消息,使得对象的引用计数-1,当此操作完毕后,引用计数为0的对象就会被系统回收了;
- 总的来说自动引用计数(ARC)就是iOS系统用来进行内存管理手段,通过监控对象的引用计数值来决定对象是否应该回收;
判断一个对象是否被回收的依据
对象的引用计数为0时,Objective-C中会调用-(void)dealloc
而Swift会调用deinit {}
;当这两个方法被正常调用时说明对象的内存管理是正确的;但是也会出现对象不被正常释放的情况,例如:两个对象互相强引用造成循环引用,使用block或是闭包造成与self的循环引用等,那么该如何解决这种强引用循环呢?
循环强引用
类的实例之间的循环强引用
@interface Person()
@property(strong)Car* car;
@end
@interface Car()
@property(strong)Person* owner;
@end
上述代码中创建Person和Car的实例后,为属性赋值,就会造成两者互为强引用,这样就使得引用计数不能为0,ARC就无法对两者进行内存的释放; 那么该如何打破这种互相强引用呢?Objective-C和Swift都提供了weak关键字的机制来解决这个问题;使用weak修饰属性在赋值的时候不会使引用计数+1,没有了强引用那么对象就能正常释放!除了weak在Swift中还提供了unowned(无主引用)解决强循环引用;
Swift中weak和unowned
当两个实例出现互相强引用:(1)实例的值为nil对逻辑上不造成影响,那么选择weak(2)实例的值的一方必须存在,那么只能使用unowned;例如人和信用卡一样,信用卡的拥有者必须要实际存在;
block和闭包中出现强循环引用
#import "HZBlock.h"
typedef void (^TestBlock)(NSString* message);
@interface HZBlock()
@property(nonatomic,copy)TestBlock testBlock;
@property(nonatomic,copy)NSString* name;
@end
@implementation HZBlock
-(void)testBlcok{
self.testBlock = ^(NSString* message){
// 循环引用
NSString* nameNew = self.name;
NSLog(@"%@",nameNew);
};
}
上述代码中在block中使用self,编译器会报警告,告知此处会出现循环引用(Capturing 'self' strongly in this block is likely to lead to a retain cycle);
在Objective-C中可以weak化self来解决此问题
// weak化self
__weak __typeof(self) weakSelf = self;
// 在block体中为了避免self被释放,可以再次强引用
__typeof(&*weakSelf) strongSelf = weakSelf;
在Swift中定义捕获列表解决闭包内的引用循环
class AutoRefManager: NSObject {
let name: String
let text: String?
lazy var asHTML: (Void) -> String = {
// 捕获列表
[unowned self] in
self.text!
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
}
总结
在开发中遇到类似的循环引用时要仔细思考,是否会造成内存泄露的问题,然后再选择合适的解决方案来解决出现的问题;