Swift中使用(RAC)自动引用计数机制,实例赋值给属性、常量或变量,都会创建此实例的强引用,只要强引用存在,实例是不允许被销毁的
实例间的循环强引用
当两个实例互相保持对方的强引用,并让对方不会被销毁,就会造成循环强引用。
例:
class School{
let classNum:Int
init(classNum:Int){
self.classNum = classNum
}
var student:Student?
deinit{
print("\(classNum)班,被析构")
}
}
class Student {
var name:String = ""
init(name:String){
self.name = name
}
var school:School?
init(closure: @escaping (_ student: Student) -> Void) {
closure(self)
}
deinit{
print("\(name)" + "被析构")
}
}
调用
//定义初始化为nil的属性
var xiaoMing:Student?
var sunMiddleSchool:School?
func setValue(){
//赋值
xiaoMing = Student(name: "小明")
sunMiddleSchool = School(classNum: 3)
//对可选实例xiaoMing和sunMiddleSchool中的school、student属性赋值(此时会造成循环强引用)
xiaoMing?.school = sunMiddleSchool
sunMiddleSchool?.student = xiaoMing
//释放实例xiaoMing和sunMiddleSchool变量持有的强引用时,引用计数不会降为0,实例也并不会被ARC销毁,析构函数也并不会被调用
//此时实例间的循环强引用阻止了实例销毁,并会造成内存泄漏
xiaoMing = nil
sunMiddleSchool = nil
}
解决实例间的循环强引用
Swift中提供了弱引用、无主引用两种方法解决使用类的属性遇到的循环强引用问题
弱引用
对于生命周期中会变为nil的实例使用弱引用,通过在定义类属性语句前添加“weak”关键字声明弱引用类属性
class School{
let classNum:Int
init(classNum:Int){
self.classNum = classNum
}
//使用"weak"关键字定义弱引用实例属性
weak var student:Student?
deinit{
print("\(classNum)班,被析构")
}
}
class Student {
var name:String = ""
init(name:String){
self.name = name
}
//使用"weak"关键字定义弱引用实例属性
weak var school:School?
init(closure: @escaping (_ student: Student) -> Void) {
closure(self)
}
deinit{
print("\(name)" + "被析构")
}
}
调用
//定义初始化为nil的属性
var xiaoMing:Student?
var sunMiddleSchool:School?
func setValue(){
//赋值
xiaoMing = Student(name: "小明")
sunMiddleSchool = School(classNum: 3)
//对可选实例xiaoMing和sunMiddleSchool中的school、student属性赋值(此时形成的是循环弱引用)
xiaoMing?.school = sunMiddleSchool
sunMiddleSchool?.student = xiaoMing
//释放实例xiaoMing和sunMiddleSchool变量持有的弱引用时,引用计数降为0,实例会被ARC销毁,析构函数会被调用
xiaoMing = nil
sunMiddleSchool = ni
}
调用结果
小明被析构
3班,被析构
无主引用
对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。
闭包引起的循环强引用
例:
class HTMLElement {
let name:String
let text:String?
lazy var asHTML:() -> String = {
if let text = self.text{
return "<\(self.name)>\(text)</\(self.name)>"
}else {
return "<\(self.name) />"
}
}
init(name:String,text:String? = nil){
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
调用
func HTMLElementFunc(){
var paragraph:HTMLElement? = HTMLElement(name: "p", text: "hello,world")
//HTMLElement 类产生了类实例和 asHTML 默认值的闭包之间的循环强引用
print(paragraph!.asHTML())
paragraph = nil
}
调用结果
<p>hello,world</p>
从调用结果看出:析构函数并未被调用说明paragraph实例并没有被释放,实例的 asHTML 属性持有闭包的强引用。但是,闭包在其闭包体内使用了self(引用了self.name和self.text),因此闭包捕获了self,这意味着闭包又反过来持有了HTMLElement实例的强引用。这样两个对象就产生了循环强引用.
解决闭包引起的循环强引用:在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。
弱引用
1.当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
2.相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用。
3.如果捕获的引用绝对不会置为nil,应该用无主引用,而不是弱引用
无主引用
通过在定义闭包时同时定义捕获列表[unowned self] in
作为闭包的一部分,来解决闭包和类实例之间的循环强引用。
class HTMLElement {
let name:String
let text:String?
lazy var asHTML:() -> String = {
C //定义闭包时同时定义捕获列表
[unowned self] in
if let text = self.text{
return "<\(self.name)>\(text)</\(self.name)>"
}else {
return "<\(self.name) />"
}
}
init(name:String,text:String? = nil){
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
调用
func HTMLElementFunc(){
var paragraph:HTMLElement? = HTMLElement(name: "p", text: "hello,world")
print(paragraph!.asHTML())
paragraph = nil
}
调用结果
<p>hello,world</p>
p is being deinitialized
根据调用结果分析:此时的析构函数被调用,实例paragraph已被释放销毁