Swift中的循环♻️引用

纵然Swift使用ARC(Automatic Reference Counting)为我们打理内存,这并不代表它面对任何情况都足够聪明。尤其是当对象之间存在相互引用的时候,更是容易由于reference cycle导致内存无法释放。当然,这并非我们本意,只是有时这样的问题发生的不甚明显。Swift为我们提供了一系列语言机制来处理reference cycle,而我们也应该时刻保持警醒,避免内存泄漏。

import UIKit
class Person {
    let name :String
    var apartment: Apartment?
    var property: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) is being initialized.")
    }
    
    deinit {
        print("\(name) is being deinitialized.")
    }
}
class Apartment {
    let unit :String
    var tenant: Person?
    unowned let owner: Person
    //和strong reference相比,unowned reference只有一个特别:不会引起对象引用计数的变化。unowned reference用于解决成员不允许为nil的reference cycle。

    init(unit: String, owner: Person) {
        self.unit = unit
        self.owner = owner
        print("Apartment \(unit) is being initialized.")
    }
    
    deinit {
        print("Apartment \(unit) is being deinitialized.")
    }
}

//: Strong reference

var ref1 :Person?
var ref2 :Person?
ref1 = Person(name: "Mars")

// count = 2
ref2 = ref1
// count = 1
ref1 = nil
// count = 0
// Mars is being deinitialized.
ref2 = nil   // is being deinitialized 销毁时调用

var mars :Person? = Person(name: "Mars")

var apt11: Apartment? = Apartment(unit: "11", owner: mars!)
mars!.apartment = apt11
// mars.count = 2
apt11!.tenant = mars

这时,尽管我们把mars和apt11设置为nil,Person和Apartmetn的deinit也不会被调用了。/因为它们的两个member(apartment和tenant)是一个strong reference,指向了彼此,让对象仍旧“存活”在内存里。
//但是,mars和apt11已经被设置成nil,我们也已经无能为力了。这就是类对象之间的reference cycle。

mars = nil
apt11 = nil
处理对象reference cycle的三种方式
    1:  weak var tenant: Person? “weak reference用于解决成员允许为nil的reference cycle。”
    2:  unowned let  “unowned reference用于解决成员不允许为nil的reference cycle。”
    3:  unowned reference和implicitly unwrapped optional配合在一起,用于解决引起reference cycle的两个成员都不允许为nil的情况。

class Country {
    let name: String
    var capital: City! // default to nil
    
    init(name: String, capitalName: String) {
        self.name = name
        // Syntax Error!!!
        self.capital = City(name: capitalName, country: self)
    }
    deinit {
        print("Country \(name) is being deinitialized.")
    }
}
class City {
    let name: String
    unowned let country: Country
    
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
    deinit {
        print("City \(name) is being deinitialized.")
    }
}
var cn: Country? = Country(name: "China", capitalName: "Beijing")
var bj: City? = City(name: "Beijing", country: cn!)

cn = nil
bj = nil
 要想构建City时,让Swift认为Country已经构造完,唯一的做法就是captical有一个默认值nil。至此,对于Capital,我们有了两个看似冲突的需求:
 
 对Country的用户来说,不能让他们知道capital是一个optional;
 对Country的设计者来说,它必须像Optional一样有一个默认的nil;
 而解决这种冲突唯一的办法,就是把capital定义为一个Implicitly Unwrapped Optional (隐式解析可选)。

处理closure和类对象之间的reference cycle

class HTMLElment {
    let name: String
    let text: String?
    
    //“lazy可以确保一个成员只在类对象被完整初始化过之后,才能使用。”
    lazy var asHTML: (Void) -> String = {
        // text
        // Capture list  由于HTMLElement没有了strong reference,因此它会被ARC释放掉,进而asHTML引用的closure也会变成“孤魂野鬼”,ARC当然也不会放过它。因此,closure和类对象间的循环引用问题就解决了。
        
        //在这里,关于closure capture list,我们要多说两点:
        //如果closure带有完整的类型描述,capture list必须写在参数列表前面;
        //如果我们要在capture list里添加多个成员,用逗号把它们分隔开;
        
        [unowned self /*, other capture member*/] () -> String in
        if let text = self.text {
            return "<\(self.name)>\(self.text)</\(self.name)>"
        }
        else {
            return "<\(self.name)>"
        }
    }
    //h1是我们定义的strong reference。Closure作为一个引用类型,它有自己的对象,因此asHTML也是一个strong reference。
    //由于asHTML“捕获”了HTMLElement的self,因此HTMLElement的引用计数是2。
    //当h1为nil时,asHTML对closure的引用和closure对self的“捕获”就形成了一个reference cycle。
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
  
    deinit {
        print("\(self.name) is being deinitialized")
    }
}
var h1: HTMLElment? = HTMLElment(name: "h1", text: "Title")
h1?.asHTML
//“当一个类中存在访问数据成员的closure member时,务必要谨慎处理它有可能带来的reference cycle问题。”
h1 = nil
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容