Swift基础10

自动引用计数

swift使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift内存管理机制会一直起作用,我们无须自己来考虑内存的管理。ARC会在类的实例不再被使用时,自动释放其占用的内存。

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

自动引用计数的工作机制

为了确保使用的实例不会被销毁,ARC会跟踪和计算每一个实例正在被多数属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。

为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称为“强”引用,是因为它会将实例牢牢的保持住,只要强引用还在,实例就不允许被销毁的。

类实例之间的循环强引用

我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。

我们可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。

class A{
    
    let name:String
    init(name:String){
        self.name = name
    }
    var b:  B?
    
    deinit{
        print("a \(name) is dead")
    }
}

class B {
    let name:String
    init(name:String) {
        self.name = name
    }
    var a:A?
    deinit{
        print("b \(name) is dead")
    }
}

func test(){
    let a = A(name: "obj a")
    let b = B(name: "obj b")
    
    a.b = b
    b.a = a
}

test()

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

Swift提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。

弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够相互引用而不产生循环强引用。

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

弱引用

弱引用不会对引用的实例保持强引用,因而不会阻止ARC销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上weak关键字声明这是一个弱引用。

在实例的生命周期中,如果某些时候引用没有值,哪么弱引用可以避免循环强引用。如果引用总是有值,则可以使用无主引用。

note: 弱引用必须被声明为变量,表明其值能在运行时被修改。弱引用不能被声明为常量。 弱引用可以没有值,我们必须将每一个弱引用声明为可选类型。在Swift中,推荐使用可选类型描述可能没有值的类型。

因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC会在引用的实例被销毁后自动将其赋值为nil。


class A{
    
    let name:String
    init(name:String){
        self.name = name
    }
    var b:  B?
    
    deinit{
        print("a \(name) is dead")
    }
}

class B {
    let name:String
    init(name:String) {
        self.name = name
    }
    weak  var a:A?
    deinit{
        print("b \(name) is dead")
    }
}

func test(){
    let a = A(name: "obj a")
    let b = B(name: "obj b")
    
    a.b = b
    b.a = a
}

test()

无主引用

和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总被定义为非可选类型。我们可以在声明属性或者变量时,在前面添加关键字unowned表示这是一个无主引用。

由于无主引用是非可选类型,我们不需要在使用它的时候将其展开。无主引用总是可以被直接访问。不过ARC无法在实例被销毁后将无主引用设为nil,因为非可选类型的变量不允许被赋值为nil。

note:如果我们试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,我们必须确保引用始终指向一个未销毁的实例。
还需要注意的是如果我们试图访问实例已经被销毁的无主引用,Swift确保程序会直接奔溃,而不会发生无法预期的行为。所以我们应当避免这样的事情发生。

class A{
    
    let name:String
    init(name:String){
        self.name = name
    }
    var b:  B?
    
    deinit{
        print("a \(name) is dead")
    }
}

class B {
    let name:String
    init(name:String,a:A ) {
        self.name = name
        self.a = a
    }
    unowned var a:A
    deinit{
        print("b \(name) is dead")
    }
}


func test(){
    let a = A(name: "obj a")
    let b = B(name: "obj b",a:a)
    
    
}

test()

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

还存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完后永远不会为nil。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。

这使两个属性在初始化完成后被直接访问(不需要可选展开),同时避免了循环引用。

class Country {
    
    let name: String
    var capitalCity: City!
    
    init(name:String,capitalCityName:String){
        self.name = name
        self.capitalCity = City(name: capitalCityName, country: self)
    }
    
    deinit{
        print("country dead")
    }
    
}

class City{
    
    let name:String
    unowned let country: Country
    
    init(name:String,country:Country){
        self.name = name
        self.country = country
    }
    deinit{
        print("city dead")
    }
}

func test(){
    var country = Country(name: "china", capitalCityName: "beijing")
    let city = country.capitalCity
    print("\(country.name) and \(country.capitalCity.name)")
    print("\(city.country.name)and \(city.country.capitalCity.name) ")
}

test()

闭包引起的循环强引用

循环强引用还会发生在当我们将一个闭包赋值给类实例的某个属性,并且这个闭包中又使用了这个类的实例。这个闭包中可能访问了实例的某个属性(self.someProperty),或者闭包中调用了实例的某个方法(self.someMethod),这两种情况都导致闭包捕获self,从而产生了循环强引用。

循环强引用的产生,是因为闭包和类相似,都是引用类型。当我们把一个闭包赋值给某个属性时,你也把一个引用赋值给这个闭包。实质上,这跟之前的问题一样的---两个强引用让彼此一有效。

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


func test(){
    var  html = HTMLElement(name: "h1", text: "hello world!")
    print(html.asHTML())
}

test()

note:虽然闭包多次使用了self,它只捕获HTMLElement实例的一个强引用。

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

在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。

note:swift有如下要求:只要闭包内使用self的成员,就要用self.someproperty或者self.someMethod(),而不是someproperty或someMethod()。这提醒我们可能一不小心就捕获了self。

定义捕获列表

捕获列表中的每一项都由一个对元素组成,一个元素是weak或unowned关键字,另外一个元素是类实例的引用(如self)或初始化过的变量(如delegate = self.delegate!)。这些项在放括号中用逗号分开。

class HTMLElement{
    
    let name:String
    let text:String?
    
    lazy var asHTML: Void ->String = {
        
        [weak weakSelf = self] in
        
        if let text = weakSelf!.text {
            return "<\(weakSelf!.name)> \(text)</\(weakSelf!.name)>"
        }else{
            return "</\(weakSelf!.name)>"
        }
        
    }
    
    init(name:String,text:String? = nil){
        self.name = name
        self.text = text
    }
    
    deinit{
        print("\(name) is dead")
    }
    
}


func test(){
    var  html:HTMLElement? = HTMLElement(name: "h1", text: "hello world!")
    print(html!.asHTML())
    html = nil
}

test()

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

lazy var someClosure: (Int,String)->String = {
        
        [unowned self,weak delegate = self.delegate!] (index:Int,StringToProcess:String)-> String in
        
        //closure statement
        
    }
    ```

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

lazy var someClosure: Void ->String = {

    [unowned self,weak delegate = self.delegate!]  in
    
    //closure statement
    
}
```

弱引用和无主引用

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

相反的,在捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会被自动设置为nil。这使我们可以在闭包体内检查它们是否存在。

note:如果被捕获的引用绝对不会变为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 dead")
    }
    
}


func test(){
    var  html:HTMLElement? = HTMLElement(name: "h1", text: "hello world!")
    print(html!.asHTML())
    html = nil
}

test()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容