Swift文档Chapter 23 自动引用计数


自动引用计数(ARC) 追踪和管理应用程序的内存。一般情况下ARC会自动释放内存。少数情况下需要使用ARC进行内存管理。引用计数仅使用在引用类型中。

工作机制

当你每次创建一个类的新的实例的时候,ARC会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
此外,当实例不再被使用时,ARC释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。
为了确保使用中的实例不会被销毁,ARC会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。

循环强引用

有时候我们可以写出两个实例循环引用。这样子即使我们将两个实例断开,他们互相之间依然有引用。不会被ARC回收。为了解决这种问题,我们可以使用弱引用或者无主引用

弱引用

当一个实例的生命周期比较短的时候可以使用弱引用。在声明属性或者变量的时候使用weak关键字表明弱引用。弱引用可以被ARC销毁,销毁后会变成nil.因此,这些变量必须命名成可选型变量,而不是常量。

ARC将属性变为nil时不会触发属性观察。

一旦一个属性没有了强引用,只有弱引用。那么这个值会被立刻销毁。

无主引用

无主引用用于和实例有相同或者更长的周期时使用。使用unowned关键字。
无主引用期望拥有值,ARC无法在销毁无主引用后赋值为nil。非可选类型不能赋值为nil
无主引用必须指向一个没有被销毁的实例。实例销毁后再访问实例的无主引用会引发错误。(没有主人后就必须销毁这个实例)

无主引用和隐式解包可选值属性

如果说两个属性都必须有值,不能为nil,那么以上两种情况都比较不适合。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解包可选值属性。

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

在这个例子中,每一个City都应当有一个对应的Country,因此需要使用无主引用。在Country中,我们使用带有感叹号的City类型声明为隐式解包可选值类型的属性。其默认值为nil,并且我们不需要展开它的值就可以访问他。这样子就可以符合两个要求。

闭包的循环强引用

将闭包赋值给类的某个属性,并且这个闭包使用了类中的属性,那么也会产生强引用。循环强引用的产生,是因为闭包和类相似,都是引用类型。
Swift提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表(closure capture list)

class HTMLElement {

    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }

}

HTMLElement强引用了闭包asHTML,但是asHTML同样也使用了对应的self的属性,因此也是一对强引用。

解决闭包的循环强引用

在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。

定义捕获列表

捕获列表中的每一项都由一对元素组成,一个元素是weakunowned关键字,另一个元素是类实例的引用(例如self)或初始化过的变量(如delegate = self.delegate!)。这些项在方括号中用逗号分开。
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:

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

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

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

弱引用和无主引用

在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用
相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在。
如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。