国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

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

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

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

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

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

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

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

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

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

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 的初始化方法中,我們生成了一個(gè) B 的實(shí)例并將其存儲(chǔ)在屬性中。然后我們又將 A 的實(shí)例賦值給了 b.a 。這樣 a.b 和 b.a 將在初始化的時(shí)候形成一個(gè)引用循環(huán)。現(xiàn)在當(dāng)有第三方的調(diào)用初始化了 A ,然后即使立即將其釋放, A 和 B 兩個(gè)類實(shí)例的 deinit 方法也不會(huì)被調(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 的這個(gè)對(duì)象,b 中的 b.a 依然引用著這個(gè)對(duì)象,導(dǎo)致它無法釋放。而進(jìn)一步,a 中也持有著 b,導(dǎo)致 b 也無法釋放。在將 obj 設(shè)為 nil 之后,我們?cè)诖a里再也拿不到對(duì)于這個(gè)對(duì)象的引用了,所以除非是殺掉整個(gè)進(jìn)程,我們已經(jīng) 永遠(yuǎn) 也無法將它釋放了。多么悲傷的故事啊..

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

為了防止這種人神共憤的悲劇的發(fā)生,我們必須給編譯器一點(diǎn)提示,表明我們不希望它們互相持有。一般來說我們習(xí)慣希望 "被動(dòng)" 的一方不要去持有 "主動(dòng)" 的一方。在這里 b.a 里對(duì) A 的實(shí)例的持有是由 A 的方法設(shè)定的,我們?cè)谥笾苯邮褂玫囊彩?A 的實(shí)例,因此認(rèn)為 b 是被動(dòng)的一方。可以將上面的 class B 的聲明改為:

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

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

A deinitB deinit

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

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

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

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

// RequestManager.swiftclass RequestManager: RequestHandler {	func requestFinished() {		println("請(qǐng)求完成")	}	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ā)送請(qǐng)求		// 一般來說會(huì)將 req 的引用傳遞給網(wǎng)絡(luò)框架	}	func gotResponse() {		// 請(qǐng)求返回		delegate?.requestFinished?()	}}

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

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

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

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 的屬性,會(huì)被 self 持有,而它本身又在閉包內(nèi)持有 self ,這導(dǎo)致了 xiaoMing 的 deinit 在自身超過作用域后還是沒有被調(diào)用,也就是沒有被釋放。為了解決這種閉包內(nèi)的循環(huán)引用,我們需要在閉包開始的時(shí)候添加一個(gè)標(biāo)注,來表示這個(gè)閉包內(nèi)的某些要素應(yīng)該以何種特定的方式來使用。可以將 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

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

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

// 標(biāo)注前{ (number: Int) -> Bool in	//...	return true}// 標(biāo)注后{ [unowned self, weak someObject] (number: Int) -> Bool in	//...	return true}	
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 宣城市| 来凤县| 平阳县| 惠来县| 新巴尔虎左旗| 名山县| 皋兰县| 华宁县| 广宁县| 怀集县| 长垣县| 辛集市| 汨罗市| 阿瓦提县| 六盘水市| 桐庐县| 镇坪县| 贵定县| 来凤县| 尖扎县| 朔州市| 平遥县| 正镶白旗| 龙胜| 唐河县| 凯里市| 潍坊市| 临漳县| 萍乡市| 清苑县| 巴楚县| 怀集县| 抚宁县| 九龙城区| 洛川县| 大姚县| 宜宾县| 东台市| 湖北省| 黑龙江省| 佛坪县|