麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

Swift開發(fā)必備技巧:內(nèi)存管理、weak和unowned

2019-11-08 02:59:58
字體:
供稿:網(wǎng)友

因?yàn)?Playground 本身會持有所有聲明在其中的東西,因此本節(jié)中的示例代碼需要在 Xcode 項(xiàng)目環(huán)境中運(yùn)行。在 Playground 中可能無法得到正確的結(jié)果。

不管在什么語言里,內(nèi)存管理的內(nèi)容都很重要,所以我打算花上比其他 tip 長一些的篇幅仔細(xì)地說說這塊內(nèi)容。

Swift 是自動管理內(nèi)存的,這也就是說,我們不再需要操心內(nèi)存的申請和分配。當(dāng)我們通過初始化創(chuàng)建一個對象時,Swift 會替我們管理和分配內(nèi)存。而釋放的原則遵循了自動引用計(jì)數(shù) (ARC) 的規(guī)則:當(dāng)一個對象沒有引用的時候,其內(nèi)存將會被自動回收。這套機(jī)制從很大程度上簡化了我們的編碼,我們只需要保證在合適的時候?qū)⒁弥每?(比如超過作用域,或者手動設(shè)為 nil 等),就可以確保內(nèi)存使用不出現(xiàn)問題。

但是,所有的自動引用計(jì)數(shù)機(jī)制都有一個從理論上無法繞過的限制,那就是循環(huán)引用 (retain cycle) 的情況。

什么是循環(huán)引用

雖然我覺得循環(huán)引用這樣的概念介紹不太應(yīng)該出現(xiàn)在這本書中,但是為了更清晰地解釋 Swift 中的循環(huán)引用的一般情況,這里還是簡單進(jìn)行說明。假設(shè)我們有兩個類 A 和 B , 它們之中分別有一個存儲屬性持有對方:

class A {	let b: B	init() {		b = B()		b.a = self	}	deinit {		PRintln("A deinit")	}}class B {	var a: A? = nil	deinit {		println("B deinit")	}}

在 A 的初始化方法中,我們生成了一個 B 的實(shí)例并將其存儲在屬性中。然后我們又將 A 的實(shí)例賦值給了 b.a 。這樣 a.b 和 b.a 將在初始化的時候形成一個引用循環(huán)?,F(xiàn)在當(dāng)有第三方的調(diào)用初始化了 A ,然后即使立即將其釋放, A 和 B 兩個類實(shí)例的 deinit 方法也不會被調(diào)用,說明它們并沒有被釋放。

func application(application: UIApplication!, 				 didFinishLaunchingWithOptions launchOptions: NSDictionary!) 				 -> Bool {	// Override point for customization after application launch.	var obj: A? = A()	obj = nil	// 內(nèi)存沒有釋放	return true}

因?yàn)榧词?nbsp;obj 不再持有 A 的這個對象,b 中的 b.a 依然引用著這個對象,導(dǎo)致它無法釋放。而進(jìn)一步,a 中也持有著 b,導(dǎo)致 b 也無法釋放。在將 obj 設(shè)為 nil 之后,我們在代碼里再也拿不到對于這個對象的引用了,所以除非是殺掉整個進(jìn)程,我們已經(jīng) 永遠(yuǎn) 也無法將它釋放了。多么悲傷的故事啊..

在 Swift 里防止循環(huán)引用

為了防止這種人神共憤的悲劇的發(fā)生,我們必須給編譯器一點(diǎn)提示,表明我們不希望它們互相持有。一般來說我們習(xí)慣希望 "被動" 的一方不要去持有 "主動" 的一方。在這里 b.a 里對 A 的實(shí)例的持有是由 A 的方法設(shè)定的,我們在之后直接使用的也是 A 的實(shí)例,因此認(rèn)為 b 是被動的一方??梢詫⑸厦娴?nbsp;class B 的聲明改為:

class B {    weak var a: A? = nil    deinit {        println("B deinit")    }}

在 var a 前面加上了 weak ,向編譯器說明我們不希望持有 a。這時,當(dāng) obj 指向 nil 時,整個環(huán)境中就沒有對 A 的這個實(shí)例的持有了,于是這個實(shí)例可以得到釋放。接著,這個被釋放的實(shí)例上對 b 的引用 a.b 也隨著這次釋放結(jié)束了作用域,所以 b 的引用也將歸零,得到釋放。添加 weak 后的輸出:

A deinitB deinit

可能有心的朋友已經(jīng)注意到,在 Swift 中除了 weak 以外,還有另一個沖著編譯器叫喊著類似的 "不要引用我" 的標(biāo)識符,那就是 unowned 。它們的區(qū)別在哪里呢?如果您是一直寫 Objective-C 過來的,那么從表面的行為上來說 unowned 更像以前的 unsafe_unretained ,而 weak 就是以前的 weak 。用通俗的話說,就是 unowned 設(shè)置以后即使它原來引用的內(nèi)容已經(jīng)被釋放了,它仍然會保持對被已經(jīng)釋放了的對象的一個 "無效的" 引用,它不能是 Optional 值,也不會被指向 nil。如果你嘗試調(diào)用這個引用的方法或者訪問成員屬性的話,程序就會崩潰。而 weak 則友好一些,在引用的內(nèi)容被釋放后,標(biāo)記為 weak 的成員將會自動地變成 nil (因此被標(biāo)記為 @ weak的變量一定需要是 Optional 值)。關(guān)于兩者使用的選擇,Apple 給我們的建議是如果能夠確定在訪問時不會已被釋放的話,盡量使用 unowned ,如果存在被釋放的可能,那就選擇用 weak 。

我們結(jié)合實(shí)際編碼中的使用來看看選擇吧。日常工作中一般使用弱引用的最常見的場景有兩個:

設(shè)置 delegate 時在 self 屬性存儲為閉包時,其中擁有對 self 引用時

前者是 Cocoa 框架的常見設(shè)計(jì)模式,比如我們有一個負(fù)責(zé)網(wǎng)絡(luò)請求的類,它實(shí)現(xiàn)了發(fā)送請求以及接收請求結(jié)果的任務(wù),其中這個結(jié)果是通過實(shí)現(xiàn)請求類的 protocol 的方式來實(shí)現(xiàn)的,這種時候我們一般設(shè)置 delegate 為 weak :

// RequestManager.swiftclass RequestManager: RequestHandler {	func requestFinished() {		println("請求完成")	}	func sendRequest() {		let req = Request()		req.delegate = self		req.send()	}}// Request.swift@objc protocol RequestHandler {	optional func requestFinished()}class Request {	weak var delegate: RequestHandler!;	func send() {		// 發(fā)送請求		// 一般來說會將 req 的引用傳遞給網(wǎng)絡(luò)框架	}	func gotResponse() {		// 請求返回		delegate?.requestFinished?()	}}

req 中以 weak 的方式持有了 delegate,因?yàn)榫W(wǎng)絡(luò)請求是一個異步過程,很可能會遇到用戶不愿意等待而選擇放棄的情況。這種情況下一般都會將 RequestManager 進(jìn)行清理,所以我們其實(shí)是無法保證在拿到返回時作為 delegate 的 RequestManager 對象是一定存在的。因此我們使用了 weak 而非 unowned ,并在調(diào)用前進(jìn)行了判斷。

閉包和循環(huán)引用

另一種閉包的情況稍微復(fù)雜一些:我們首先要知道,閉包中對任何其他元素的引用都是會被閉包自動持有的。如果我們在閉包中寫了 self 這樣的東西的話,那我們其實(shí)也就在閉包內(nèi)持有了當(dāng)前的對象。這里就出現(xiàn)了一個在實(shí)際開發(fā)中比較隱蔽的陷阱:如果當(dāng)前的實(shí)例直接或者間接地對這個閉包又有引用的話,就形成了一個 self -> 閉包 -> self 的循環(huán)引用。最簡單的例子是,我們聲明了一個閉包用來以特定的形式打印 self 中的一個字符串:

class Person {	let name: String	lazy var printName: ()->() = {		println("The name is /(self.name)")	}	init(personName: String) {		name = personName	}	deinit {		println("Person deinit /(self.name)")	}}func application(application: UIApplication!, 		didFinishLaunchingWithOptions launchOptions: NSDictionary!) 		-> Bool {	// Override point for customization after application launch.	var xiaoMing: Person = Person(personName: "XiaoMing")	xiaoMing.printName()	return true}// 輸出:// The name is XiaoMing

printName 是 self 的屬性,會被 self 持有,而它本身又在閉包內(nèi)持有 self ,這導(dǎo)致了 xiaoMing 的 deinit 在自身超過作用域后還是沒有被調(diào)用,也就是沒有被釋放。為了解決這種閉包內(nèi)的循環(huán)引用,我們需要在閉包開始的時候添加一個標(biāo)注,來表示這個閉包內(nèi)的某些要素應(yīng)該以何種特定的方式來使用??梢詫?nbsp;printName 修改為這樣:

lazy var printName: ()->() = {    [weak self] in    if let strongSelf = self {        println("The name is /(strongSelf.name)")    }}

現(xiàn)在內(nèi)存釋放就正確了:

// 輸出:// The name is XiaoMing// Person deinit XiaoMing

如果我們可以確定在整個過程中 self 不會被釋放的話,我們可以將上面的 weak 改為unowned ,這樣就不再需要 strongSelf 的判斷。但是如果在過程中 self 被釋放了而printName 這個閉包沒有被釋放的話 (比如 生成 Person 后,某個外部變量持有了 printName ,隨后這個 Person 對象被釋放了,但是 printName 已然存在并可能被調(diào)用),使用 unowned 將造成崩潰。在這里我們需要根據(jù)實(shí)際的需求來決定是使用 weak 還是 unowned 。

這種在閉包參數(shù)的位置進(jìn)行標(biāo)注的語法結(jié)構(gòu)是將要標(biāo)注的內(nèi)容放在原來參數(shù)的前面,并使用中括號括起來。如果有多個需要標(biāo)注的元素的話,在同一個中括號內(nèi)用逗號隔開,舉個例子:

// 標(biāo)注前{ (number: Int) -> Bool in	//...	return true}// 標(biāo)注后{ [unowned self, weak someObject] (number: Int) -> Bool in	//...	return true}	
發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 91av国产在线| 国产日韩在线观看视频 | 欧美性生活xxxxx| 久久另类视频 | 国产亚洲精品久久久久久网站 | 国产精品视频导航 | 国产精品成人久久 | 黄色一级电影网 | av大全在线免费观看 | 精品国产一区二区三区成人影院 | 蜜桃传媒视频麻豆第一区免费观看 | 精品国产91久久久久 | 久久国产精品小视频 | 91美女视频在线观看 | 日韩精品久久久久久 | 黄片一级毛片 | av在线播放免费观看 | 久久精品探花 | 媚药按摩痉挛w中文字幕 | 欧美在线小视频 | 久久99精品久久久久久秒播蜜臀 | 4480午夜 | 国产1区2区3区中文字幕 | 国产一级一区二区三区 | 大学生一级毛片在线视频 | 草莓福利视频在线观看 | 欧美激情 在线播放 | 高清国产福利 | 国产精品久久久久久久久久电影 | 双性精h调教灌尿打屁股的文案 | 国产韩国精品一区二区三区久久 | 姑娘第四集免费看视频 | 国产一级免费不卡 | 国产乱乱视频 | 亚洲国产精品久久久久 | 韩国草草影院 | 草草免费视频 | 国产精品美女久久久久久不卡 | 欧美一级黄色免费看 | 激情小说色 | 国产日韩在线观看一区 |