Swift中的内存管理
2016-05-18 22:22
127 查看
ARC与GC
很多人分不清ARC(Automatic Reference Counting,自动引用计数)跟GC(Garbage Collection,垃圾收集)的区别。其实“引用计数法”也算是一种GC策略,只不过我们现在提到GC的时候一般是指基于“标记-整理”策略的垃圾收集器,譬如主流的JVM(Java虚拟机)几乎都是采用“标记-整理”+“分代收集”的策略来进行自动内存管理的。标记算法一般是从全局对象图的“根”出发进行可达性分析,对象的生死会被批量地标记出来,之后再在某个时间批量地释放死对象。显然,这是一种“全局+延时”的管理策略。而与之相对的,引用计数是一种“局部+即时”的内存管理策略。它不需要全局的对象信息,一般每个被管理的对象都会跟一个引用计数器关联,这个计数器保存着当前对象被引用的次数,一旦创建一个新的引用指向该对象,引用计数就加1,每当指向该对象的某个引用失效引用计数就减1,直到引用计数为0,就立即释放该对象。使用引用计数法管理内存的语言也不止OC和Swift,还有诸如CPython之类的GC也是基于引用计数的。
早年OC是采用MRC(手动引用计数)的,当然其实现在也有人还在用,它跟ARC的主要区别在于它需要手动管理引用计数器,而ARC是自动管理的。所以其实MRC也不能让你直接释放对象的,只是控制引用罢了。
循环引用
上面解释了一下ARC的运作方式,从中不难看出这种策略的缺陷,就是循环引用问题。看下图:object1和object1之间形成了循环引用,它们的引用计数始终为1,始终不会被释放,这就造成了内存泄漏。“标记-整理”策略并不会出现这种问题,因为哪怕两个对象相互引用,但只要它们和“根”对象失去了联系,照样会被标记为死对象,然后在合适的时间被释放。
实例分析
接下来看一个稍微复杂一点的实例,分析一下出现循环引用的原因然后给出解决方法。class SimpleRefreshCtrl: UIRefreshControl { typealias Action = () -> () var action: Action! init(action: Action) { super.init() tintColor = UIColor.navigationBarColor() self.action = action self.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:)has not been implemented") } func refresh() { self.action() delay(seconds: 1) { self.endRefreshing() } } }
这是我自己封装的一个下拉刷新控制器,它继承自
UIRefreshControl,可以在
UITableViewController中直接使用,如下:
class HouseTableCtrl: UITableViewController { //... func getPageData() { getListFromApi(urlString){ json, nextLink in self.houseData = json self.page = nextLink } } override func viewDidLoad() { super.viewDidLoad() let refreshCtrl = SimpleRefreshCtrl (action: getPageData) self.refreshControl = refreshCtrl } }
这样,当你下拉列表的时候,旋转的菊花就会出现旋转1秒,同时执行
getPageData方法,刷新页面数据。
但是这里出现了循环引用问题,我们来看看它是怎么发生的。在
getPageData方法中我调用了一个全局函数
getListFromApi,而这个全局函数需要一个闭包作为参数,而这个闭包又捕获了当前对象的两个属性,也就持有了当前对象的引用。到这里为止并没有什么问题,虽然闭包捕获外部变量从而持有外部对象的引用经常是造成循环引用的一大元凶,但在这里,该闭包是个匿名闭包,我们的
HouseTableCtrl对象并没有持有该闭包的引用,所以问题并不是出在这里。
接下来,在初始化
SimpleRefreshCtrl对象的时候,
getPageData作为参数被传递了过去,并被赋值给
SimpleRefreshCtrl的实例属性
action。注意,
getPageData是在
HouseTableCtrl中定义的一个实例方法,是跟当前的
HouseTableCtrl对象关联的,作为参数传递过去的实际上是
self.getPageData。如此一来,
SimpleRefreshCtrl对象就持有了当前
HouseTableCtrl对象的引用。然后接下来这一句
self.refreshControl = refreshCtrl,持有
HouseTableCtrl对象引用的
SimpleRefreshCtrl对象被赋值给了
HouseTableCtrl的实例属性
refreshControl,于是
HouseTableCtrl对象也持有了
SimpleRefreshCtrl对象的引用。这就造成了循环引用。
要如何打破僵局呢,其实也很简单,使用
weak或者
unowned就行了:
//refreshCtrl指向的对象只持有当前HouseTableCtrl对象的一个弱引用 let refreshCtrl = SimpleRefreshCtrl { [weak self]in self?.getPageData() } //这一句强引用self.refreshControl = refreshCtrl
这样
SimpleRefreshCtrl对象就只是持有当前
HouseTableCtrl对象的一个弱引用,弱引用是不算在
HouseTableCtrl对象的引用计数中的,也就是说当没有其他引用指向
HouseTableCtrl对象时,
HouseTableCtrl对象能被正常释放,一旦
HouseTableCtrl对象被释放了,那
SimpleRefreshCtrl对象也就能被正常释放了:
至于
weak和
unowned该用哪个么,看情况了,
weak修饰的属性或变量是一个optional类型,也就是说是可以为nil的。而
unowned则是修饰一个nonoptional,是不能为nil的,一旦这个属性或变量指向的对象被释放了(这是有可能发生的,因为unowned引用也是不算在引用计数中的,如果除了unowned引用外没有其他引用指向那个对象,那它将被释放),而你还想使用该对象的话,将会触发runtime
error,程序也就crash了。所以个人来说,我是更推荐使用
weak的。
相关文章推荐
- Apple Swift学习教程
- 介绍 Fedora 上的 Swift
- iOS开发之路--微博“更多”页面
- Swift中实现点击、双击、捏、旋转、拖动、划动、长按手势的类和方法介绍
- Swift自定义iOS中的TabBarController并为其添加动画
- Swift编程中的泛型解析
- Swift中定义二维数组的方法及遍历方法示例
- 简单分析Swift语言的一些基本特征
- 使用 Swift 语言编写 Android 应用入门
- Swift与C语言指针结合使用实例
- Swift心得笔记之控制流
- 用Swift构建一个简单的iOS邮件应用的方法
- 苹果公司推出的新编程语言Swift简介和入门教程
- Swift实现iOS应用中短信验证码倒计时功能的实例分享
- iOS开发之路--微博骨架搭建
- iOS开发使用JSON解析网络数据
- IOS开发代码分享之获取启动画面图片的string
- iOS开发实现音频播放功能
- iOS开发之视图切换