在swift语言中,提供了两种可以解决强引用循环的方法,就是当我们应用在类类型的属性中的时候,有弱引用(weak reference
)和无主引用(unowned reference
)两种特殊的引用方法,
弱引用和无主引用都会使引用循环(reference cycle
)中的一个实例引用到另一个其他的实例,而并不会强烈地保留在该实例上。此时这些实例就可以相互引用对方而并不会形成一个强引用循环,
使用弱引用只有当我们确保另一个实例活的比较短,也就是说我们创建了实例A只是偶尔会引用实例B,在此时我们就可以在B身上使用弱引用,这样就会告诉ARC该实例可以被第一次使用之后释放。在上面的Apartment
例子中,对于apartment来说,在某时某个点有0个tenant是完全合情合理的。所以在这种情况下使用弱引用也是完全合情合理的。与此相反,使用无主引用只有当一个和另一个实例拥有相同使用期,要么都活的一样长,要么都活的一样短,这就是使用弱引用和无主引用的区别了。
弱引用就像上面简单介绍的那样,一个弱引用并不会很强有力的保留它引用的那个实例很久很久,因此ARC并不会阻止销毁被引用的实例,这种行为预防了引用成为强引用循环的一部分。我们可以给变量或属性的前面以添加weak关键字的方式来指明一个弱引用。
因为弱引用不会强烈的保留在它引用的实例上,所以说有可能在该实例还在被弱引用的时候就会被ARC自动释放了。因此 ARC会自动将该弱应用设置为nil
,当引用的实例被释放之时。因为弱引用在允许的时候允许它自己的值被更改为nil
,总是用可选类型的变量定义弱引用的,而不是常量。我们可以用检查其他可选值的方法那样检查一个弱引用中是否存在某个值,我们也将永远不会访问一个不存在的实例
下面这个例子是和上面Person和Apartment例子是一样的,但是有一点是不相同的,此刻,Apartment类型的tenant属性定义为弱引用了。
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
两个之前就创建的变量(john
和 unit4A
)链接在一起形成强引用。
var john: Person?
var unit4A: Apartment?
// 创建实例
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
// 引用形成 使用!是因为其为可选类型
john!.apartment = unit4A
unit4A!.tenant = john
下面是这两个变量定义为弱引用的图例关系。
该Person实例对Apartment实例来说依然采用了强引用,而此时Apartment实例对Person实例采用的是弱引用,这就意味着,我们可以以给变量john设置nil值的方式来段来强引用。对于Peson实例来说就在也没有强引用了,
john = nil
// Prints "John Appleseed is being deinitialized"
因为现在Person实例来说没有强引用循环了(被释放了),tenant被设置为nil了。强引用断开了,而Apartment实例对Person实例采用的弱引用也就失去了意义。
唯一剩下的Apartment实例的强引是来自于unit4A变量的,如果此时在给该变量设置为nil,那么将不会有任何引用了。
unit4A = nil
// Prints "Apartment 4A is being deinitialized"
两个变量都被设置为nil,则两个实例变量之间再也没有任何引用了。
需要注意的是:在系统中使用垃圾收集机制,弱引用有时候会实现简单的缓冲机制(caching mechanism),只有当垃圾收集机制被触发的时候没有强引用的对象才会被释放。然而,在ARC里只要最后一次强引用被断开,值才会被释放,该机制符合弱引用的气质。
和弱引用一样,无主引用是一个不会强烈保留在它所引用的实例上的一种引用。与弱引用不同的是,使用无主引用的时候,只有当其他实例有相同的使用期,或更长的使用期,我们可以在属性火变量的定义前面添加unowned
关键字,来指明我们所定义的无主引用。
于弱引用不同的是,无主引用通常都被期望拥有一个值。不过ARC无法在实例被销毁后将无主引用设为nil ,因为非可选类型的变量不允许被赋值为nil
。
下面这个例子定义了两个类Customer和CreditCard,用来模拟银行里的顾客和他的信用卡操作。这两个类里面,一个类可以将另一个类的实例作为属性来存储起来。不过这种关系可能会造成强引用循环。
Customer和CreditCard的例子与上面的弱引用Apartment和Person的例子还是有一点点不一样的。在数据模型里,一个客户可能有或可能没有信用卡。但是每一张信用卡都会关联一个客户,一个实例CreditCard从来都不会超过它引用的客户,也就是说一个人对应一张信用卡。为了表达这种关系,Customer类有一个可选的card属性,但是CreditCard有一个无主的(非可选)Customer属性。
此外,只能通过一个number值和一个customer实例传递给customer构造器的方式来创建CreditCard实例。这样就能确保每创建一个CreditCard实例就必须要与之关联一个Customer实例,也就是说创建一个信用卡实例,就必须要有与信用卡过关联的客户实例。因为信用卡实例总是要有一个客户实例,我们可以将customer属性定义为无主引用,这样就会避免强引用循环。
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
下面这段代码定义了一个变量john,该变量是一个可选的Customer类型。用来存储一个给特定客户的一个引用。默认情况下, 该变量有一个初始值nil
。
// 一个类型为可选的Customer的变量john,它的初始值为nil
var john: Customer?
我们现在可以创建一个Customer实例,并且用它来构造并设定一个CreditCard实例,用做客户的card属性。
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
Customer实例现在对CreditCard实例有一个强引用,并且CreditCard实例现在对Customer实例有一个无主引用。因为有了无主引用,所以说当我们断开由变量john发出的强引用,在变量john和Customer实例之间就没有强引用了。
因为没有任何强引用通向Customer实例了,也就是说customer实例被释放掉了,Customer实例被释放掉了也就意味着没有任何强引用通向CreditCard实例了,所以CreditCard实例也会被释放掉。
john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"
最后上面这段代码向我们展示了Customer和CreditCard实例的析构器,和输出的文字信息,当变量john被重新设置为nil的时候,也就意味之没有任何一个强引用通向任何一个Customer实例。