Swift学习笔记十五

自动引用计数(Automatic Reference Counting)

和OC一样,Swift用自动引用计数机制来跟踪和管理你应用程序的内存,大多数情况下,你不需要考虑自己管理内存,Swift会自动帮你管理。当实例对象不再需要时,Swift会自动释放它使用的内存。但是,在有些情况下,ARC需要知道更多你代码之间的关系来帮助你管理内存,本章会描述那些情况并展示如何启用ARC来管理你应用程序的内存。

注意:引用计数只作用于类实例,结构体和枚举是值类型而非引用类型,并且不是以引用形式被存储和传递的。

每当你创建一个新的类实例对象,ARC为它分配一块内存用以存储比如实例类型、实例相关联的存储式属性的值等。当实例不再被需要时,ARC会销毁实例,释放其所占用的内存以供他用。如果实例已经被销毁,那么就不能再使用它或者访问它的属性、方法。为了确保还被需要的实例不被销毁,ARC追踪有多少属性、常量及变量引用到了一个类实例,只要还有至少一个引用指向实例,ARC就不会销毁它。这是通过“强引用”实现的,当你将一个实例赋值给一个常量或者变量、属性时,那个常量或者变量、属性会与实例之间建立一个“强引用”,之所以成为“强”,是因为这个引用会只要这个引用还存在,那么这个实例就不能被释放销毁。

“强引用”在带来便利的时候,会产生一个问题,就是“强引用循环”,比如实例A的某个属性引用了实例B,而实例B的某个属性又引用了A,那么即使其他任何变量都没有引用A和B,他们也各自被一个强引用持有,那么即便他们都不再被需要,它们也不会被销毁,这就造成了内存泄露。

为了解决个这个问题,可以使用“弱引用(weak reference)”和“不持有引用(unowned reference)”。它们可以使你在引用某个实例对象的时候不会持有它,这样实例在互相引用的时候就不会产生强引用循环。

当引用在其生命周期内的某个时刻可能会是nil的时候,使用“弱引用”,当你确定某个引用在其生命周期内都不可能是nil的时候,使用“不持有引用”。因为弱引用是允许没有值的,因此它只能被赋值给可选类型(optional type)。

“弱引用”只能赋值给变量,不能赋值给常量,这是为了表明在运行时其值可能会发生变化的,当弱引用指向的对象已经被销毁时,ARC将弱引用的值改为nil。比如:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}
 
class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    weak var tenant: Person? //这里通过weak前缀来声明弱引用变量属性
    deinit { println("Apartment #\(number) is being deinitialized") }
}

“不持有引用”和“弱引用”类似,不过它确保永远有值,因此,它总是被赋值给非可选类型,在访问它的时候,也不需要像可选类型那样展开,而是直接访问。此外,当其指向的对象已经被释放时,ARC不能将“不持有引用”设为nil,因为非可选类型的变量不能被设置为nil。事实上,当你访问一个指向已经被销毁对象的不持有引用时,会触发一个运行时错误。

当把一个闭包赋值给一个实例对象的属性,而闭包内部又引用这个实例时(比如闭包体访问实例的属性值self.property或者访问实例的方法self.method),也会发生“强引用循环”,这是因为闭包和类一样,是引用类型的。Swift提供一种优雅的方式来打破这种“强引用循环”,比如:

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 {
        println("\(name) is being deinitialized")
    }
    
}

这里HTMLElement类定义了一个lazy属性asHTML,它的默认值是一个闭包,因为它是属性而不是方法,因此你可以用自定义的函数或者闭包来取代默认值,这里属性被标记为lazy,因为它不需要在一开始就被赋值,只有在需要的时候(即需要生成HTML代码),才会执行这个闭包。正因为它是lazy的属性,因此在闭包体内可以访问self,因为执行它的时候初始化已经完成了。

要解决闭包的强引用循环问题,需要用到“捕获列表”,在定义闭包的时候同时定义其捕获列表,这个列表定义了闭包体内捕获一个或多个引用类型时所遵循的规则。

捕获列表是用一堆中括号定义,其内部的项用逗号隔开,每一个项都是一个关键字(weak或unowned)与一个类实例引用(或者一个变量的定义)组成的对,将捕获列表放在其参数列表之前,如下:

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}

这样就解除了闭包和实例对象之间的强引用循环。

posted @ 2015-07-21 17:36  Dson  阅读(277)  评论(0编辑  收藏  举报