@escaping
在Swift3中,閉包默認(rèn)是非逃逸的。在Swift3之前,事情是完全相反的:那時(shí)候逃逸閉包是默認(rèn)的,對于非逃逸閉包,你需要標(biāo)記@noescaping。Swift3的行為更好。因?yàn)樗J(rèn)是安全的:如果一個(gè)函數(shù)參數(shù)可能導(dǎo)致引用循環(huán),那么它需要被顯示地標(biāo)記出來。@escaping標(biāo)記可以作為一個(gè)警告,來提醒使用這個(gè)函數(shù)的開發(fā)者注意引用關(guān)系。非逃逸閉包可用被編譯器高度優(yōu)化,快速的執(zhí)行路徑將被作為基準(zhǔn)而使用,除非你在有需要的時(shí)候顯式地使用其他方法。
@escaping標(biāo)明這個(gè)閉包是會(huì)“逃逸”,通俗點(diǎn)說就是這個(gè)閉包在函數(shù)執(zhí)行完成之后才被調(diào)用。為了體現(xiàn)@escaping的作用,我們在之前先做一個(gè)鋪墊:
func doWork(block:()->()) {
print("header")
block()
print("footer")
}
doWork {
print("work")
}
//控制臺(tái)打印的消息如下:
//header
//work
//footer
對于上述的block調(diào)用是同步行為。我們修改一下代碼,將block放到一個(gè)異步操作中,讓它在doWork返回后被調(diào)用。這個(gè)時(shí)候我們就需要用@escaping標(biāo)記表明這個(gè)閉包是會(huì)“逃逸”的。
func doWorkAsync(block: @escaping () -> ()) {
DispatchQueue.main.async {
block()
}
}沒有逃逸的閉包的作用域是不會(huì)超過函數(shù)本身的,所以說我們不需要擔(dān)心在閉包內(nèi)持有self。逃逸的閉包就不同了,因?yàn)樾枰_保閉包內(nèi)的成員依然有效,如果在閉包內(nèi)引用self以及self的成員的話,就要考慮閉包內(nèi)持有self的情況了。
class S {
var foo = "foo"
func method1() {
doWork {
print(foo)
}
foo = "bar"
}
func method2() {
doWorkAsync {
print(self.foo)
}
foo = "bar"
}
func method3() {
doWorkAsync {
[weak self] _ in
print(self?.foo)
}
foo = "bar"
}
}
S().method1()// foo
S().method2()// bar
S().method3()// nil
method1不需要考慮self .foo的持有情況,而method2需要考慮,我們讓閉包持有了self,打印的值就是foo賦值之后的內(nèi)容bar,如果我們不希望閉包內(nèi)持有self的話,可以使用[weak self]的方法來表示. method3就是這樣,在閉包執(zhí)行的時(shí)候已經(jīng)沒有了對實(shí)例對象的引用,所有說輸出是nil。
weak 和 unowned
上面我用的是 [weak self] ,如果用[unowned self]表示的話,method3就需要稍做修改:
func method3() {
doWorkAsync {
[unowned self] _ in
print(self.foo)
}
foo = "bar"
}
這兩者都是用來防止循環(huán)引用的,但是還是有一點(diǎn)小小的區(qū)別:unowned 有點(diǎn)像oc里面的unsafe_unretained,而weak就是以前的weak。對于這兩者的使用,不能說用哪一個(gè)要好一點(diǎn),要視情況而定。用unowned的話,即使它原來的引用的內(nèi)容被釋放了,它仍然會(huì)保持對被已經(jīng)釋放了的對象的一個(gè)引用,它不能是Optional也不能是nil值,這個(gè)時(shí)候就會(huì)出現(xiàn)一個(gè)問題,如果你調(diào)用這個(gè)引用方法或者訪問成員屬性的話,就會(huì)出現(xiàn)崩潰。而weak要稍微友善一點(diǎn),在引用的內(nèi)容被釋放之后,會(huì)自動(dòng)將weak的成員標(biāo)記為nil。有人要說,既然這樣,那我全部使用weak。但是在可能的情況下,我們還是應(yīng)該傾向于盡量減少出現(xiàn)Optional 的可能性,這樣有助于代碼的簡化。Apple給我們的建議是如果能夠確定訪問時(shí)不會(huì)被釋放的話,盡量用unowned,如果存在被釋放的可能性的話,就用weak。
在Playground里面進(jìn)行異步操作
上面的代碼如果你在Playground里面運(yùn)行的話,你會(huì)發(fā)現(xiàn)method2,method3方法里面的回調(diào)不會(huì)走。不會(huì)走的原因大概是來不及走,三個(gè)函數(shù)一執(zhí)行完,程序就終止了,異步來不及執(zhí)行。怎么來解決這個(gè)問題呢?在Xcode 8 里面我們可以import PlaygroundSupport,在調(diào)用方法前設(shè)置PlaygroundPage.current.needsIndefiniteExecution = true就好了。
PlaygroundPage.current.needsIndefiniteExecution = true
S().method1()// foo
S().method2()// bar
S().method3()// nil