自动引用计数

OC相同 Swift使用自动引用计数(ARC)来跟踪和管理应用程序内存

注意

引用计数仅仅应用于类的实例。结构体和枚举是值类型,不是引用类型,也不是通过引用的方式存储和传递的

工作机制

创建类的实例时,ARC会分配一块内存来存储实例信息.不再使用该实例,ARC将其释放.当是实例被释放后,其方法或属性均不能被访问或调用。
因此,为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。

循环强引用

如果两个类实例互相引用,那么类实例的强引用数永远不能变为0,这就是循环强引用

解决循环强引用方法

解决办法: 弱引用 无主引用

将循环引用中一个实例对另外的实例不保持强引用

当其他的实例有更短的生命周期时,使用弱引用,即其 实例 析构在先时; 当其他实例有相同或者更长的声明周期时,请使用无主引用

弱引用

弱引用不会对其引用的实例保持强引用,因而不会阻止ARC销毁被引用的实例。

声明属性或变量时,在前面加上weak关键字表明是个弱引用

ARC会在引用的实例被销毁后自动将其赋值为nil.并且弱引用可以允许它们的值运行时赋值为nil,所以会被定义为可选类型变量而不是常量

弱引用为可选类型变量,可以通过检查是否为nil,防止访问已经销毁的实例的引用

注意

ARC设置弱引用为nil时,属性观察不会被触发

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") }
}

var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
//因为Apartment 中 的 tenant 属性为弱引用,因此即使这样也不会产生循环引用

john = nil
// 打印 “John Appleseed is being deinitialized”

unit4A = nil
// 打印 “Apartment 4A is being deinitialized”

变量johnunit4A在被赋值为nil后,Person实例和Apartment实例的析构函数都打印出“销毁”的信息

注意:

在使用垃圾收集的系统里

无主引用

在属性前加unowned关键字进行声明无主引用

与弱引用类似,无主引用不会保持住引用的实例
与弱引用不同,无主引用用在与其他实例有相同或者更长声明周期时使用

无主引用为非可选类型,因此ARC无法在实例被销毁后将无主引用设置为nil

注意:

使用无主引用,必须保证引用始终指向一个未销毁的实例
试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误

举个栗子:
Customer客户和CreditCard银行卡之间的关系,两个类都将另外一个类的实例作为自身属性,关系是客户可能没有银行卡,但是银行卡一定有客户,因此将银行卡的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 //确保在32、64位机器上均可以保存16位的卡号
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}


var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

john = nil
// 打印 “John Appleseed is being deinitialized”
// 打印 ”Card #1234567890123456 is being deinitialized”

Customer实例持有对CreditCard实例的强引用,而CreditCard实例持有对 Customer实例的无主引用

注意:

上面是展示如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况(例如:处于次年功能方面原因),Swift还提供了不安全的无主引用。与所有的不安全操作类似,需要负责检查代码确保其安全性。可以通过unowned(unsafe)声明不安全的无主引用。如果视图在实力被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作

无主引用以及隐式解析可选属性

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

我们在CountrycapitalCity属性声明为City!隐式解析可选类型的属性,表明像其他可选类型一样,默认值为nil,但是不需要展开他的值就能访问

因为capitalCity的默认值为默认为nil,因此在构造country时,当name赋值以后,初始化就完成了,此时我们就能把这个初始化的country实例传递给City来构造City实例

闭包引起的循环强引用

闭包和类 类似,都是引用类型,因此,将闭包赋值为某属性时,是将这个闭包的引用赋值给属性,因此当闭包体中访问实例的某个属性时,就会造成循环引用

class HTMLElement {
    let name: String
    let text: String?
    lazy var asHTML: Void -> 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")
    }
}

asHTML声明为lazy属性,因为只有当元素确实需要被处理为HTML输出的字符串时,才需要使用asHTML。也就是说,在默认的闭包中可以使用self,因为只有当初始化完成以及self确实存在后,才能访问lazy属性。

闭包捕获列表解决闭包引起的循环引用

对于一个类的闭包属性,想要改变这个属性的行为,可以通过给这个属性再赋值一个闭包

HTMLElement有类和作为asHTML默认值的闭包之间的循环强引用

此时HTMLElement实例和它的闭包是不会被销毁和释放的

注意:

虽然闭包中多次使用self,但是它只捕获HTMLElement实例的一个强引用

解决闭包引起的循环强引用

在定义闭包时同时定义捕获列表作为闭包的一部分,捕获列表定义了闭包体内捕获一个或多个引用类型的规则.声明捕获的引用为弱引用或者无主引用,而不是强引用

注意:

Swift规定:在闭包中使用self的成员,要使用self.someProperty或者self.someMethod(),而不是使用somePropertysomeMethod()
这是为了提醒你 一不小心就捕获了self

定义捕获列表

捕获列表由一对元素组成,一个是weakunowned关键字,另一个是类实例的引用如self或初始化的变量.

在方括号中用逗号隔开

如果闭包有参数列表和返回类型,把捕获列表放在它们前面:

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // 这里是闭包的函数体
}

/**
如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,
那么可以把捕获列表和关键字 in 放在闭包最开始的地方
*/

lazy var someClosure: Void -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // 这里是闭包的函数体
}

弱引用和无主引用

在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。(即被捕获的引用不会变为nil使用无主引用)
在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用,弱引用总是可选类型,在引用的实例被销毁后自动置为nil

注意

如果被捕获的引用绝对不会变成nil,应该用无主引用,而不是弱引用

class HTMLElement {
    let name: String
    let text: String?
    lazy var asHTML: Void -> String = {
        [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")
    }
}

asHTML闭包中多了一个捕获列表,[unowned self]将self声明为无主引用
这时候没有了循环引用,我们就可以销毁HTMLElement实例了