14-Swift自動引用計數(shù)(循環(huán)引用的解決)

swift使用自動引用計數(shù)(ARC)機制來跟蹤和管理應用程序的內(nèi)存速侈。一般情況下,swift內(nèi)存管理機制會一直起作用,即開發(fā)者無需考慮內(nèi)存管理废封。ARC會在類的實例不再使用時,即沒有引用的時候丧蘸,自動釋放其所占用的內(nèi)存漂洋。
?但是要注意,引用計數(shù)僅僅應用在類的實例力喷,因為結(jié)構(gòu)體和枚舉類型是值類型刽漂,不是引用類型,也不是通過引用的方式存儲和傳遞的弟孟。


一贝咙、自動引用計數(shù)的工作機制


當在創(chuàng)建一個類的實例時,ARC會分配一個內(nèi)存用于存儲實例的信息拂募。內(nèi)存中會包含實例的類型信息庭猩,以及這個實例中所有相關屬性的值窟她。
?當實例不再被使用時,ARC會釋放該實例所占用的內(nèi)存眯娱,并讓釋放的內(nèi)存挪作他用礁苗。這即是確保了不再被使用的實例,不會一直占用內(nèi)存空間徙缴。
?為了確保使用中的實例不會被銷毀试伙,ARC會跟蹤和計算每個實例正在被多少屬性、變量和常量所引用于样,只要是實例還有被引用疏叨,ARC就不會銷毀該實例。

不論是將實例賦值給屬性穿剖、常量還是變量蚤蔓,它們都會創(chuàng)建對此實例的強引用強引用就是將實例牢牢的保持住糊余,只要是有強引用秀又,實例就不會被銷毀!!!


二、自動引用計數(shù)實踐


以下是展示自動引用計數(shù)的工作機制:

class Student {
    let name:String
    init(name:String) { // 構(gòu)造器
        self.name = name;
        print("名字的初始值為:\(name)");
    }
    
    deinit {    // 析構(gòu)器
        print("實例將會被釋放 --- \(name)");
    }
}

// 定義三個學生變量贬芥,類型是Student?吐辙,注意是可選類型
var student1:Student?;
var student2:Student?;
var student3:Student?;
// 實例化,注意這里就是一個強引用
// // 此時Student實例強引用計數(shù)為1
student1 = Student(name: "張三");
print("學生姓名: \(student1!.name)");
// 賦值操作
student2 = student1; // 此時Student實例強引用計數(shù)為2
student3 = student1; // 此時Student實例強引用計數(shù)為3
// 通過設置變量為`nil`蘸劈,即是斷開強引用
student1 = nil; // 此時Student實例強引用計數(shù)為2
student2 = nil; // 此時Student實例強引用計數(shù)為1

// 注意看打印效果昏苏,此時Student實例是沒有被使用的,因為`deinit`析構(gòu)器中代碼還沒被調(diào)用

// 當student3設置為`nil`時威沫,此時Student實例強引用計數(shù)為0
student3 = nil;
// 當沒有強引用的時候贤惯,才會被銷毀

輸出結(jié)果:
名字的初始值為:張三
學生姓名: 張三
實例將會被釋放 --- 張三


三、類實例之間引起的循環(huán)引用


如果兩個類實例相互持有對方的強引用棒掠,這即是循環(huán)引用孵构。解決辦法就是通過定義類之間關系為弱引用無主引用,以代替強引用(下面會有實際解決實例句柠,這里先了解循環(huán)引用是怎么產(chǎn)生的)浦译。

/** 
 Student學生類: 具體某個學生,哪個班級
 Grade班級類: 具體班級溯职,班級里的學生有誰
 */
class Student {
    // 學生名字
    let name:String
    // 構(gòu)造器
    init(name:String) {
        self.name = name
    }
    
    // 哪個班級
    var grade:Grade?
    
    // 析構(gòu)器
    deinit {
        print("實例將會被釋放 --- \(name)");
    }
}

class Grade {
    // 哪個班級
    let gradeName:String
    //構(gòu)造器
    init(gradeName:String) {
        self.gradeName = gradeName
    }
    
    // 班級中的學生
    var student:Student?

    // 析構(gòu)器
    deinit {
        print("實例將會被釋放 --- \(gradeName)");
    }
}

// 學生: 張三
var student:Student? = Student(name: "張三")
// 班級: 一年一班
var grade:Grade? = Grade(gradeName: "一年一班")

// 張三是在一年一班
student!.grade = grade
// 一年一班中的學生
grade!.student = student

// 設置為`nil`精盅,因為它們都是可選類型的
student = nil
grade = nil

// 但是Student和Grade的析構(gòu)器都沒調(diào)用,說明它們的實例是沒有被釋放

開始谜酒,實例化操作叹俏,那么此時對應類的實例都只有一個強引用所指向(圖中實線就表示強引用):

01-實例化操作

之后,是賦值操作student!.grade = gradegrade!.student = student僻族,此時:
02-賦值操作

最后粘驰,設置student = nilgrade = nil屡谐,此時:
03-設置為`nil`操作

從上可以看到,StudentGrade的實例都是有強引用蝌数,即實例不會被釋放掉愕掏,那么這也就會造成內(nèi)存的泄漏。


四顶伞、解決實例之間的循環(huán)引用


swift提供了兩種辦法用來解決循環(huán)引用的問題:弱引用無主引用饵撑。
?弱引用和無主引用允許循環(huán)引用中的一個實例引用和另一個實例而不是保持強引用,這也就不會產(chǎn)生循環(huán)引用問題唆貌。
?對于生命周期中會為nil的實例使用弱引用滑潘。但對于初始化值后不會再被賦值為nil的實例,則使用無主引用锨咙。

  • 弱引用语卤,是不會阻止ARC銷毀被引用的實例,或者說弱引用是不計數(shù)是不會加一操作的酪刀。當聲明屬性或變量時粹舵,在前面加上weak關鍵字表明一個弱引用。
// 注: 與上面代碼一樣骂倘,只是在定義student屬性時齐婴,進行了弱化操作
class Grade {
    // 哪個班級
    let gradeName:String
    // 構(gòu)造器
    init(gradeName:String) {
        self.gradeName = gradeName
    }
    
    // 班級中的學生
    // var student:Student?
    // 弱引用操作,解決實例之間的循環(huán)引用
    weak var student:Student?

    // 析構(gòu)器
    deinit {
        print("實例將會被釋放 --- \(gradeName)");
    }
}

04-弱引用操作

當設置student = nilgrade = nil的時候(注意圖中標注的先后順序稠茂。另外只要實例沒有強引用,那么會被釋放):
05-實例釋放過程

注意1: 弱引用必須被聲明為變量情妖,表明其值能在運行時被修改2枪亍!毡证!另外电爹,弱引用可以沒有值,所以也必須將弱引用聲明為可選類型料睛。而swift中也是推薦使用可選類型來描述可能沒有值的類型丐箩。
注意2: 在使用垃圾收集的系統(tǒng)里,弱指針有時候來實現(xiàn)簡單的緩沖機制恤煞,因為沒有強引用的對象只會在內(nèi)存壓力觸發(fā)垃圾收集時才會被銷毀屎勘。但ARC中,一旦值的最后一個強引用被刪除居扒,就會被立即銷毀概漱,這導致弱引用并不適合上面的用途。

  • 無主引用喜喂,與弱引用類似瓤摧,無主引用也不會牢牢保持住引用的實例竿裂。但與弱引用不同的是,無主引用永遠是有值的U彰帧D逡臁!因此無主引用總是被定義為非可選類型这揣。而在聲明屬性或變量時悔常,在前面加上unowned關鍵字來表示是一個無主引用。由于無主引用是非可選類型曾沈,你不需要再使用它的時候?qū)⑺归_这嚣,無主引用總是可以被直接訪問。但ARC無法再實例被實例銷毀后將無主引用設置為nil塞俱,因為非可選類型的變量不允許被賦值為nil姐帚。

注意: 如果你試圖在實例被銷毀后,訪問該實例的無主引用障涯,會導致程序崩潰罐旗。使用無主引用,必須要確保引用始終指向一個未銷毀的實例唯蝶。

/** 無主引用
 例如銀行顧客Customer和信用卡CreditCard九秀;
 一個人可以有或沒有信用卡,但一張信用卡必須與一個客戶關聯(lián)粘我;
 */
// 銀行顧客
class Customer {
    // 用戶名
    let name:String
    // 用戶對應的信用卡鼓蜒,可選類型表示可以有卡,也可以沒有卡
    var card:CreditCard?
    
    init(name:String) {
        self.name = name
    }
    deinit {
        print("銀行顧客-實例被銷毀 --- \(self.name)")
    }
}

// 信用卡
class CreditCard {
    // 卡號
    let number:UInt64
    // 無主引用征字,避免循環(huán)引用(非可選類型都弹,信用卡實例必須與一個客戶關聯(lián))
    unowned let customer:Customer
    
    init(number:UInt64, customer:Customer) {
        self.number = number
        self.customer = customer
    }
    deinit {
        print("信用卡-實例被銷毀 --- \(self.number)")
    }
}

// 銀行客戶,張三(可選類型匙姜,是可以設置為`nil`)
var zhangsan:Customer? = Customer(name:"張三");
// 張三的信用卡【注意畅厢,初始化的時候?qū)⒖ㄌ柵c張三實例傳進去】
zhangsan!.card = CreditCard(number: 1234_5678_9101_1121,customer: zhangsan!)

// 設置為空,即斷開對Customer實例的引用氮昧,檢查是否會正常釋放
zhangsan = nil

輸出結(jié)果:
銀行顧客-實例被銷毀 --- 張三
信用卡-實例被銷毀 --- 1234567891011121

銀行客戶與信用卡綁定后框杜,它們之間的關系:

01-顧客與信用卡之間關聯(lián)

之后,設置zhangsan = nil袖肥,此時Customer實例張三沒有強引用咪辱,那么會被釋放。而Customer實例張三釋放以后椎组,CreditCard實例也就沒有強引用梧乘,也會被釋放,如圖所示:
02-實例釋放過程

  • 無主引用以及隱式解析可選屬性。StudentGrade示例中选调,兩個屬性的值都允許為nil夹供,并存在循環(huán)引用,這種場景最適合弱引用解決仁堪;CustomerCreditCard示例中哮洽,一個屬性允許設置為nil,而另外一個屬性是不允許為nil弦聂,并存在循環(huán)引用鸟辅,這種常見最適合無主引用解決;而當兩個屬性都必須有值莺葫,并初始化完成后將都不會為nil匪凉,這種場景需要一個類使用無主屬性,另外一個類使用隱式解析可選屬性來解決:
/** 
 無主引用以及隱式解析可選屬性
 Country國家和City城市捺檬;
 每個國家必須有首都再层,而每個城市必須屬于一個國家;
 */
// 國家類
class Country {
    // 國家名
    let name:String
    // 首都城市堡纬,是隱式解析可選類型(默認是為空聂受,這是不展開都可使用)
    var capitalCity:City!
    init(name:String, capitalName:String) {
        // 指定國家名
        self.name = name
        // 指定首都城市,傳入城市名以及國家實例
        self.capitalCity = City(name:capitalName, country: self)
    }  
    deinit {
        print("Country實例被釋放 --- \(name)")
    }
}
// 城市類
class City {
    // 城市名
    let name:String
    // 所屬首都烤镐,是無主類型
    unowned let country:Country
    
    init(name:String, country:Country) {
        self.name = name
        self.country = country
    }
    deinit {
        print("City實例被釋放 --- \(name)")
    }
}
var country = Country(name: "中國", capitalName: "北京")
print("\(country.name) - \(country.capitalCity.name)")
輸出結(jié)果:
中國 - 北京
01-無主引用以及隱式解析可選屬性


五蛋济、閉包引起的循環(huán)引用


在閉包賦值給類實例的某個屬性時也會有強引用。閉包體中可能訪問實例的某個屬性炮叶,例如self.someProperty碗旅,或者閉包調(diào)用了實例中的某個方法,例如self.someMethod镜悉,這都會導致閉包"捕獲"self,從而產(chǎn)生了循環(huán)引用:

/** 
 閉包的循環(huán)引用
 */
class Person {
    // 名字
    let name:String
    
    // 懶加載扛芽,自我介紹
    lazy var speakStr:Void -> String = {
        // 閉包中
        return "大家好,我叫\(zhòng)(self.name)"
    }
    
    
    init(name:String) {
        self.name = name
    }
    deinit {
        print("Person實例釋放 --- \(name)");
    }
}


// 初始化
var zhangsan:Person? = Person(name: "張三")
// 調(diào)用
print(zhangsan!.speakStr())

// 斷開對Person實例的引用积瞒,但此時Person實例不會被銷毀
zhangsan = nil
01-閉包中的強引用


六、解決閉包引起的循環(huán)引用


對于閉包所引起的循環(huán)引用登下,在swift中提供了一種解決辦法茫孔,即是閉包捕獲列表。在定義閉包同時定義捕獲列表作為閉包的一部分被芳,通過這種方式可以解決閉包和類實例之間的循環(huán)引用缰贝。捕獲列表定義了閉包體內(nèi)捕獲一個或多個引用類型的規(guī)則,這與解決兩個類之間的循環(huán)引用一樣畔濒,聲明每個捕獲的引用為弱引用或無主引用剩晴,而不是強引用。
?在閉包中需要注意的是,只要在閉包中要使用本對象中的屬性或方法赞弥,就必須要使用self.somePropertyself.someMethod()毅整,而不能使用somePropertysomeMethod()
?定義捕獲列表绽左,在捕獲列表中的每一項都由需要加上weakunowned關鍵字悼嫉。
?在閉包和捕獲的實例總是相互引用時并且是同時銷毀時,可以將閉包內(nèi)的捕獲定義為無主引用拼窥。
?而在被捕獲的引用可能會為nil時戏蔑,將閉包內(nèi)的捕獲定義為弱引用。弱引用總是可選類型鲁纠,并且當引用的實例被銷毀后总棵,弱引用的值會自動置為nil
?如果是被捕獲的引用絕對不會變?yōu)?code>nil改含,應該用無主引用情龄,而不是引用。

// 閉包有參數(shù)列表和返回類型的定義候味,把捕獲列表放在前面
lazy var someClosure:(Int, String) -> String = {
  [unowned self, weak delegate = self.delegate!] (index:Int ,stringToProcess:String) -> String in
    // 閉包中對應操作
}
// 閉包沒有指定參數(shù)列表或返回類型刃唤,即會通過上下文推斷,那么可以把捕獲列表和關鍵字`in`放在閉包最開始地方
lazy var someClosure:Void -> String = {
  [unowned self, weak delegate = self.delegate!] in 
    // 閉包中對應操作
}
class Person {
    // 名字
    let name:String
    
    // 懶加載白群,自我介紹
    lazy var speakStr:Void -> String = {
        // 解決循環(huán)引用尚胞,即表示"用無主引用而不是強引用來捕獲self"
        [unowned self] in
        // 閉包中
        return "大家好,我叫\(zhòng)(self.name)"
    }
    
    
    init(name:String) {
        self.name = name
    }
    deinit {
        print("Person實例釋放 --- \(name)");
    }
}


// 初始化
var zhangsan:Person? = Person(name: "張三")
// 調(diào)用
print(zhangsan!.speakStr())

// 斷開對Person實例的引用帜慢,此時Person實例就會被銷毀
zhangsan = nil
輸出結(jié)果:
大家好笼裳,我叫張三
Person實例釋放 --- 張三
01-閉包循環(huán)引用的解決


注:xcode7.3環(huán)境

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市粱玲,隨后出現(xiàn)的幾起案子躬柬,更是在濱河造成了極大的恐慌,老刑警劉巖抽减,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件允青,死亡現(xiàn)場離奇詭異,居然都是意外死亡卵沉,警方通過查閱死者的電腦和手機颠锉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來史汗,“玉大人琼掠,你說我怎么就攤上這事⊥W玻” “怎么了瓷蛙?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵悼瓮,是天一觀的道長。 經(jīng)常有香客問我艰猬,道長横堡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任姥宝,我火速辦了婚禮翅萤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腊满。我一直安慰自己套么,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布碳蛋。 她就那樣靜靜地躺著胚泌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肃弟。 梳的紋絲不亂的頭發(fā)上玷室,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音笤受,去河邊找鬼穷缤。 笑死,一個胖子當著我的面吹牛箩兽,可吹牛的內(nèi)容都是我干的津肛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼汗贫,長吁一口氣:“原來是場噩夢啊……” “哼身坐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起落包,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤部蛇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咐蝇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涯鲁,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年有序,在試婚紗的時候發(fā)現(xiàn)自己被綠了抹腿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡笔呀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出髓需,到底是詐尸還是另有隱情许师,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站微渠,受9級特大地震影響搭幻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逞盆,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一檀蹋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧云芦,春花似錦俯逾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至琉历,卻和暖如春坠七,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旗笔。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工彪置, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蝇恶。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓拳魁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艘包。 傳聞我的和親對象是個殘疾皇子的猛,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容