当前位置: 首页 > 编程学习 > 其它语言 > Swift > 正文

Swift4 自动引用计数,例子代码

2018-04-22 来源:博客园/CC_Joy

自动引用计数

苹果官方指南 Automatic Reference Counting

苹果官方指南翻译 自动引用计数

ARC的工作机制

ARC 管理实例占用的内存,用时分配,不用时释放。通过跟踪计算实例的引用数是否为0,判断是否要释放其内存。释放其占用内存,即被销毁。

使实例一直存在,不被销毁的引用,都是强引用,

ARC实例

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}
 
var reference1: Person?
var reference2: Person?
var reference3: Person?
 
reference1 = Person(name: "John Appleseed")
// prints "John Appleseed is being initialized"
 
reference2 = reference1
reference3 = reference1
 
reference1 = nil
reference2 = nil
 
reference3 = nil
// prints "John Appleseed is being deinitialized"

Person实例有三个强引用,必须全部断开,ARC才会销毁它。

类实例之间的循环强引用

如果两个类实例彼此持有对方的强引用,每个实例都让对方一直存在,就是循环强引用。

解决办法就是定义类之间的关系为弱引用或无主引用。

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 }
    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
 
john = nil
unit4A = nil

由于两个类实例join和unit4A之间循环强引用,就是赋值为nil,也不会被ARC销毁。

解决实例之间的循环强引用

两个办法,弱引用weak,无主引用unowned,两者都允许应用不保持强引用。

对于生命周期中会变为 nil 的实例使用弱引用。 对于初始化赋值后再也不会被赋值为 nil 的实例,使用无主引用。

弱引用

声明属性或变量前,使用 weak 关键字。
弱引用允许值为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
 
john = nil
// prints "John Appleseed is being deinitialized"

无主引用

声明属性或变量前,使用 unowned 关键字。
无主引用是非可选类型,不能设为nil。

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") }
}
 
var john: Customer?
 
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
 
john = nil
// prints "John Appleseed is being deinitialized"
// prints "Card #1234567890123456 is being deinitialized"

无主引用和隐式展开的可选属性

两个属性都必须有值,并且初始化完成后永远不会为 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
    }
}
 
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// prints "Canada's capital city is called Ottawa"

只有 Country 的实例完全初始化完后, Country 的初始化器才能把 self 传给 City 的初始化器。

通过在类型结尾处加上感叹号( City! )的方式,以声明 Country 的 capitalCity 属性为一个隐式展开的可选属性。

闭包的循环强引用

当你把闭包赋值给了一个属性,你实际上是把一个引用赋值给了这个闭包,造成循环强引用。解决办法为闭包捕获列表。

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 {
        print("\(name) is being deinitialized")
    }
    
}
 
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
    return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// prints "<h1>some default text</h1>"
 
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// prints"hello, world"
 
paragraph = nil

实例的 asHTML 属性持有闭包的强引用。闭包在其闭包体内使用了 self ,两个对象就产生了循环强引用。

设置 paragraph 变量为 nil,注意 HTMLElement 的反初始化器中的消息并没有被打印。

解决闭包的循环强引用

定义捕获列表

把捕获列表放在形式参数和返回类型前边

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

如果闭包没有指明形式参数列表或者返回类型,那么就把捕获列表放在关键字 in 前边,闭包最开始的地方

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

弱引用和无主引用

在闭包和捕获的实例总是互相引用并且总是同时释放时,将闭包内的捕获定义为无主引用。

相反,在被捕获的引用可能会变为 nil 时,定义一个弱引用的捕获。弱引用总是可选项,当实例的引用释放时会自动变为 nil 。这使我们可以在闭包体内检查它们是否存在。

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> 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")
    }
    
}
 
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// prints "<p>hello, world</p>"
 
paragraph = nil
// prints "p is being deinitialized"