当每次创建新实例时,ARC会分配一块内存,用于储存实例信息。当实例不再使用时,ARC便会释放实例所占用的内存。
引用计数
那怎么判断实例是否还在使用?在Swift中,ARC会跟踪和计算实例正在被多少个变量或常量所引用,只要还有一个变量或常量保持实例的“强引用”,那么就说实例还在使用中,不能释放其内存。
一旦计算实例不再使用,便会调用实例的析构函数“deinit”
class Person {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("person is deinit")
}
}
var person: Person? = Person(name: "Johan")
var person1 = person
var person2 = person
person1 = nil // 没有执行deinit
person2 = nil // 没有执行deinit
person = nil // 执行deinit
类之间循环引用问题
在Java的知道,判断实例是否还在使用并不使用引用计数的方式,因为引用计数的方式有一个致命的问题,就是类的循环引用,简单的来说,实例A持有实例B,实例B持有实例A,导致A,B实例都不能释放。
有这么一个例子,居民和公寓的关系。居民可以住在公寓上,也可以住在其他地方;公寓可以有居民,也可能是空房子,两者的关系都是可选关系,可有可没有。
class Person {
var apartment: Apartment?
deinit {
print("person is deinit")
}
}
class Apartment {
var person: Person?
deinit {
print("apartment is deinit")
}
}
var person: Person? = Person()
var apartment: Apartment? = Apartment()
person?.apartment = apartment
apartment?.person = person
apartment = nil // 并没有执行apartment实例的deinit方法
person = nil // 并没有执行person实例的deinit方法
如例,虽然apartment为nil了,但是person中还持有apartment实例的引用,所以apartment实例不能被释放,person同理。要解决这种循环引用,使用的方式弱引用。(在Java也是这么解决的,所以很容易理解)
弱引用
在变量前加“weak”
class Person {
var apartment: Apartment?
deinit {
print("person is deinit")
}
}
class Apartment {
// 弱引用,可选使用弱引用
weak var person: Person?
deinit {
print("apartment is deinit")
}
}
var person: Person? = Person()
var apartment: Apartment? = Apartment()
person?.apartment = apartment
apartment?.person = person
apartment = nil // 执行apartment实例的deinit方法
person = nil // 执行person实例的deinit方法
例子中,apartment实例一旦为nil,person中的apartment引用也跟着释放,所以apartment实例被引用为0,所以可以释放apartment实例的内存。因为apartment都已经释放了,也就不再持有person的引用,一旦person为nil,同样被引用数为0,ARC便会释放person实例的内存。这样就解决了类之间的循环引用问题!
无主引用
在变量前加“unowned”
在Swift中,只有可选变量才能修改为nil,如果有一种情况,也属于类之间的循环引用,这就得用无主引用来解决了。
如这么一个例子,个人和银行卡之间的关系。个人可以没有银行卡,也可以有银行卡;但是银行卡必须属于一个人。两者的关系是,一个可有可无(可选),一个是必须有(非可选)。
class Custom {
var card: BankCard?
deinit {
print("custom is deinit")
}
}
class BankCard {
// 无主引用,非可选使用无主引用
unowned var custom: Custom
init(custom: Custom) {
self.custom = custom
}
deinit {
print("bank card is deinit")
}
}
var johan: Custom? = Custom()
var abcCard: BankCard? = BankCard(custom: johan!)
johan?.card = abcCard
johan = nil // 执行johan实例的deinit方法
abcCard = nil // 执行abcCard实例的deinit方法
解决闭包引起的循环引用问题
我们可以在闭包使用“self”关键字访问实例的属性值或者实例方法,这是由于闭包拥有“捕获”上下文的能力,而实例又持有闭包(闭包是引用类型,感觉有点像匿名内部类)的引用,这就引起单个类中循环引用的问题。
class HTMLElement {
var name: String
var text: String?
lazy var asHtml: (Void) -> String = {
if let text = self.text {
return "<\(self.name)>\(self.text)<\(self.name)>"
} else {
return "<\(self.name)"
}
}
init(name: String) {
self.name = name
}
deinit {
print("html element is deinit")
}
}
var element: HTMLElement? = HTMLElement(name: "p")
element?.text = "hello"
element?.asHtml
element = nil // 并没有执行element实例的deinit方法
由于闭包asHtml持有self实例,所以就算element置为nil,也不能释放内存。解决办法是定义捕获列表:
class HTMLElement {
var name: String
var text: String?
lazy var asHtml: (Void) -> String = {
// 定义捕获列表,并设置为无主引用(语法倒是很奇怪)
[unowned self] (Void) -> String in // (Void) -> String 可省略,为了看得清楚点
if let text = self.text {
return "<\(self.name)>\(self.text)<\(self.name)>"
} else {
return "<\(self.name)"
}
}
init(name: String) {
self.name = name
}
deinit {
print("html element is deinit")
}
}
var element: HTMLElement? = HTMLElement(name: "p")
element?.text = "hello"
element?.asHtml
element = nil // 执行element实例的deinit方法
我们定义了捕获列表之后,element置为nil后,就可以正常被释放了。