循环引用
循环引用在iOS开发中是需要非常重视的一个问题,不合理的循环引用会导致内存的泄漏,这在开发中是非常危险的一件事。
循环引用是由于二个对象互相强引用着对方,导致双方无法释放,在Swift中由于structs结构体是值类型所以不存在循环引用,而class类由于是引用类型,使用不当很容易造成循环引用。
class Window {
var rootView: View?
}
class View {
var window: Window
init(window: Window) {
self.window = window
}
}
var window: Window? = window()//引用计数: window 1
var view: View? = View(window: window!)//引用计数:window:2 ,view: 1
window?.rootView = view//引用计数: window: 2, view: 2
view = nil//引用计数: window: 2, view: 1
window = nil//window: 1, view: 1
上面虽然将view和window二个变量置为了nil,但是由于二个对象的相互强应用,使得引用计数无法清零,都无法释放。
打破循环引用
-
Weak References弱引用:通过weak定义弱引用,一旦被引用的对象被销毁则置为nil,所以必须是可选类型,且是通过var定义。 -
Unowned References无主引用: 通过unowner定义无主引用,和弱引用作用一样,但不强制是可选类型;由于对象销毁后仍然存储这对象的内存地址(俗称僵尸对象),所以试图在对象销毁后访问无主引用,会产生运行时错误(野指针)。
weak的使用:
class Window {
weak var rootView: View?//使用weak
deinit {
print("Deinit Window")
}
}
class View {
var window: Window
init(window: Window) {
self.window = window
}
deinit {
print("Deinit View")
}
}
var window: Window? = window()//引用计数: window 1
var view: View? = View(window: window!)//引用计数:window:2 ,view: 1
window?.rootView = view//引用计数: window: 2, view: 1
view = nil//引用计数: window: 1, view: 0
window = nil//window: 0, view: 0
/*Deinit View
Deinit Window
*/
使用weak后,由于window不在强引用view,所以当view = nil时,view就得到释放,由于view在var view: View? = View(window: window!)让window引用计数加1,随着view的释放,window引用计数减1,接着随着window = nil使得window引用计数清零得到释放。
注意:将
window = nil放在view = nil前面不影响打印的顺序,依然是View先释放。
unowned的使用:
class Window {
var rootView: View?//使用weak
deinit {
print("Deinit Window")
}
}
class View {
unowned var window: Window
init(window: Window) {
self.window = window
}
deinit {
print("Deinit View")
}
}
var window: Window? = window()//引用计数: window 1
var view: View? = View(window: window!)//引用计数:window:2 ,view: 1
window?.rootView = view//引用计数: window: 2, view: 1
view = nil//引用计数: window: 1, view: 0
window = nil//window: 0, view: 0
/*Deinit View
Deinit Window
*/
使用unowned咋一看效果和weak是一样的,其实都达到了打破循环引用的目的,但是使用unowned时我们需要保证非强引用的对象的生命周期不小于强引用它的对象(注意上面是先将view置为nil,在将window置为nil)。
var window: Window? = Window()
var view: View? = View(window: window!)
window?.rootView = view
window = nil
let viewTwo = view?.window//在window置为nil后访问无主引用,代码会在此崩溃
view = nil
/*
Deinit Window
Fatal error: Attempted to read an unowned reference but object 0x1007435d0 was already deallocated
*/
总结
- 无主引用和弱引用都能打破循环引用,弱引用要求是var变量且是可选类型,无主引用则没有要求,但是需要特别注意引用于被引用对象的生命周期以防访问了僵尸对象造成程序崩溃。
- 无主引用比弱引用的效率要高一些,只有能保证非强引用的对象的生命周期不小于强引用它的对象,就可以放心的使用无主引用,例如父对象控制子对象的生命周期,而且其他对象不知这个子对象的存在。
- 一般开发中我们通常使用弱引用,因为团队开发中在重构代码时无法保证生命周期原则,尽管无主引用的效率相对较高。