iOS中容易出现循环引用从而造成内存泄露及如何避免

435 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

回想以前在swift语言还没有问世的时候我们仍然使用的是Objective-C语言去开发iOS项目,在我们后来用swift开发以来我们会发现其实循环引用无论是Objective-C还是在swift中是都很容易出现,下面我来回忆一下都是在什么场景出现循环引用,我们用什么方式可以避免循环引用的出现。

循环引用容易出现的场景及解决方案

1. block使用self容易造成循环引用

  • 举一个Objective-C的例子代码
dispatch_queue_t lockQueue = dispatch_queue_create("com.test.LockQueue", NULL);
dispatch_sync(lockQueue, ^{
    [self doSomething];
});

从上面的代码中可以看到在一个同步执行的队列的回调block中执行我们接下来的操作使用了self,在这种情况下持有queue的类就和这个queue形成了一个强制循环引用,造成的后果就是这个类不会被及时释放从而造成内存泄露。那么我们应该怎么避免或者怎么防止这种情况出现呢?那么就可以从我们的弱引用weak来说起,下面请看示例代码

__weak typeof (self) weakSelf = self;
dispatch_queue_t lockQueue = dispatch_queue_create("com.test.LockQueue", NULL);
dispatch_sync(lockQueue, ^{
    [weakSelf doSomething];
});

和上边有循环引用的代码相比,应该一眼就能看出写法的不同,我们使用了__weak typeof (self)这种写法获取到了queue所在类的弱引用,这样在block中使用的时候就不会对当前类造成强引用更不会造成循环引用,这样就可以防止因为循环引用造成的内存泄露。

  • swift语言的block中造成的循环引用的出现以及解决方案。

出现问题的代码(范例代码)

someClass.doFunc() {
    self.doMethod()
}

其实在开始用swift写代码之后就没有再怎么用过Objective-C写过代码,因为swift太好用了,写起来比Objective-C语言可是太清楚轻松了,上边这段代码就是直接在block中使用了self去调用该类中的一个方法,可能你会感觉这样用没什么问题啊,但通过打印日志(可以自己去写个类实际操作一下)你会发现在你不再使用这个类的时候它竟然在调用完以上代码后自己并不会在应该释放的时候被释放掉反而是被retain了造成了循环引用,那么swift中如何避免block中使用self的循环引用呢,看如下代码

someClass.doFunc() { [weak self] in
    self?.doMethod()
}

同样是和Objective-C中一样使用了weak弱引用这种方式避免循环引用造成的内存泄露。

2. 类和类之间相互引用造成的循环引用

  • Objective-C中类和类之间互相引用容易造成循环引用

例如在一个ViewController中引用你定义的一个自己定义的ViewModel这样的一个属性

@interface MyViewController : NSObject
@property (nonatomic, strong) MyViewModel *viewModel;
...
@end

@interface MyViewModel : NSObject
@property (nonatomic, strong) MyViewController * viewController;
...
@end

在MyViewController中引用了MyViewModel,在MyViewModel中也引用了MyViewController,在MyViewModel中引用MyViewController无论使用strong还是assign去修饰都会造成强制引用,如果这个时候MyViewController已经关闭释放掉了,但由于之前的循环引用就会造成了双方都会在应该释放的时候仍然不能正确释放掉从而造成了内存泄露,那么为了能够解决掉这个问题我们又需要用到weak关键字来解决这个问题,例如如下代码:

@interface MyViewController : UIViewController
@property (nonatomic, strong) MyViewModel *viewModel;
...
@end

@interface MyViewModel : NSObject
@property (nonatomic, weak) MyViewController * viewController;
...
@end

将MyViewModel中引用MyViewController的修饰改为weak这样在MyViewController释放以后不会和MyViewModel造成循环引用。

  • swift中类和类之间互相引用造成的循环引用

swift中造成循环引用的情况其实和Ojbective-C中类似如代码

class MyViewController : UIViewController
    var viewModel:MyViewModel?
...
@end

class MyViewModel
    var viewController:MyViewController?
...
@end

以上代码是会很容易造成循环引用的,你可能会疑问我两边都定义成可选类型为什么还会循环引用呢?对是都定义的可选类型,但这种写法在MyViewController释放的时候你必须在MyViewModel中手动释放一下viewController这个变量才可以,也就是设置viewController = nil才会释放ViewModel对ViewController的引用才不会因为循环引用造成内存泄露。如果在MyViewModel中引用MyViewController使用unowned去修饰就会避免循环引用,以前我老用weak来做这个事,但是发现并不太行。

class MyViewController : UIViewController
    var viewModel:MyViewModel?
...
@end

class MyViewModel
    unowend var viewController:MyViewController?
...
@end

总结

其实为了防止循环引用造成的内存泄露我们只要细心一点找对方法就可以避免这种情况的出现,主要要注意block使用时对self的使用一定要使用弱引用,在类和类互相引用作为副类引用主类一定要使用unowned(swift)关键字修饰weak(OC)关键字修饰,最好是能做到在每个副类尽量写一个自己释放资源的方法在确定主类需要释放时候调用释放方法将需要释放的对象置空即设置为nil,这样可以有效避免循环引用造成的内存泄露。