逃逸閉包 & 非逃逸閉包
逃逸閉包定義
當(dāng)閉包作為一個實際參數(shù)傳遞給一個函數(shù)
時,并且是在函數(shù)返回之后調(diào)用
,我們就說這個閉包逃逸了。當(dāng)聲明一個接受閉包作為形式參數(shù)的函數(shù)時外邓,可以在形式參數(shù)前寫@escaping
來明確閉包是允許逃逸的
- 如果
用@escaping
修飾閉包后,我們必須顯示的在閉包中使用self
- swift3.0之后古掏,系統(tǒng)默認閉包參數(shù)就是被
@noescape
损话,可以通過SIL來驗證
非逃逸閉包的特點:
- 1、執(zhí)行時機:在函數(shù)體內(nèi)執(zhí)行
- 2冗茸、閉包生命周期:函數(shù)執(zhí)行完之后,閉包也就消失了
逃逸閉包的兩種調(diào)用情況
- 1匹中、作為屬性存儲夏漱,在后面進行調(diào)用
- 2、延遲調(diào)用
1顶捷、作為屬性
當(dāng)閉包作為存儲屬性
時挂绰,主要有以下幾點說明:
- 1、
定義
一個閉包屬性
- 2服赎、在
方法中對閉包屬性進行賦值
- 3葵蒂、在
合適的時機調(diào)用
(與業(yè)務(wù)邏輯相關(guān))
如下所示,當(dāng)前的complitionHandler
作為HTTeacher
的屬性重虑,是在方法makeIncrementer
調(diào)用完成后才會調(diào)用践付,這時,閉包的生命周期要比當(dāng)前方法的生命周期長
//*********1缺厉、閉包作為屬性
class HTTeacher {
//定義一個閉包屬性
var complitionHandler: ((Int)->Void)?
//函數(shù)參數(shù)使用@escaping修飾永高,表示允許函數(shù)返回之后調(diào)用
func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){
var runningTotal = 0
runningTotal += amount
//賦值給屬性
complitionHandler = handler
}
func doSomething(){
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("HTTeacher deinit")
}
}
//使用
var t = HTTeacher()
t.doSomething()
t.complitionHandler?(20)
//<!--打印結(jié)果-->
//20
2、延遲調(diào)用
-
【延遲方法中使用】 1提针、在
延遲方法
中調(diào)用逃逸閉包
class HTTeacher {
//定義一個閉包屬性
var complitionHandler: ((Int)->Void)?
let serialQueue = DispatchQueue(label: "networkRequests")
//函數(shù)參數(shù)使用@escaping修飾命爬,表示允許函數(shù)返回之后調(diào)用
func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){
var runningTotal = 0
runningTotal += amount
//賦值給屬性
self.complitionHandler = handler
//延遲調(diào)用
DispatchQueue.global().asyncAfter(deadline: .now()+0.1) {
print("逃逸閉包延遲執(zhí)行")
handler(runningTotal)
}
print("函數(shù)執(zhí)行完了")
}
func doSomething(){
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("HTTeacher deinit")
}
}
//使用
var t = HTTeacher()
t.doSomething()
//---打印結(jié)果
函數(shù)執(zhí)行完了
HTTeacher deinit
逃逸閉包延遲執(zhí)行
10
當(dāng)前方法執(zhí)行的過程中不會等待閉包執(zhí)行完成后再執(zhí)行,而是直接返回
辐脖,所以當(dāng)前閉包的生命周期要比方法長
逃逸閉包 vs 非逃逸閉包 區(qū)別
-
非逃逸閉包:一個接受閉包作為參數(shù)的函數(shù)饲宛,閉包是在這個函數(shù)結(jié)束前內(nèi)被調(diào)用,即可以理解為
閉包是在函數(shù)作用域結(jié)束前被調(diào)用
- 1嗜价、
不會產(chǎn)生循環(huán)引用
艇抠,因為閉包的作用域在函數(shù)作用域內(nèi)幕庐,在函數(shù)執(zhí)行完成后,就會釋放閉包捕獲的所有對象 - 2练链、針對非逃逸閉包翔脱,
編譯器會做優(yōu)化
:省略內(nèi)存管理調(diào)用 - 3、非逃逸閉包捕獲的上下文
保存在棧上
媒鼓,而不是堆上(官方文檔說明)届吁。
- 1嗜价、
-
逃逸閉包:一個接受閉包作為參數(shù)的函數(shù),逃逸閉包可能會在函數(shù)返回之后才被調(diào)用绿鸣,即
閉包逃離了函數(shù)的作用域
- 1疚沐、
可能會產(chǎn)生循環(huán)引用
,因為逃逸閉包中需要顯式的引用self
(猜測其原因是為了提醒
開發(fā)者潮模,這里可能會出現(xiàn)循環(huán)引用了)亮蛔,而self可能是持有閉包變量的(與OC中block的的循環(huán)引用類似) - 2、一般用于異步函數(shù)的返回擎厢,例如網(wǎng)絡(luò)請求
- 1疚沐、
-
使用建議:如果沒有特別需要究流,開發(fā)中使用
非逃逸閉包是有利于內(nèi)存優(yōu)化
的,所以蘋果把閉包區(qū)分為兩種动遭,特殊情況時再使用逃逸閉包
自動閉包
有下面一個例子芬探,當(dāng)condition
為true
時,會打印錯誤信息厘惦,即如果是false
偷仿,當(dāng)前條件不會執(zhí)行
//1、condition為false時宵蕉,當(dāng)前條件不會執(zhí)行
func debugOutPrint(_ condition: Bool, _ message: String){
if condition {
print("debug: \(message)")
}
}
debugOutPrint(true, "Application Error Occured")
- 如果字符串是在某個業(yè)務(wù)邏輯中獲取的酝静,會出現(xiàn)什么情況?
func debugOutPrint(_ condition: Bool, _ message: String){
if condition {
print("debug: \(message)")
}
}
func doSomething() -> String{
print("doSomething")
return "Network Error Occured"
}
//---如果傳入true
debugOutPrint(true, doSomething())
//----打印結(jié)果
//doSomething
//debug: Network Error Occured
//---如果傳入false
debugOutPrint(false, doSomething())
//---打印結(jié)果
//doSomething
通過結(jié)果發(fā)現(xiàn)羡玛,無論是傳入true還是false别智,當(dāng)前的方法都會執(zhí)行
,如果這個方法是一個非常耗時的操作稼稿,這里就會造成一定的資源浪費亿遂。所以為了避免這種情況,需要將當(dāng)前參數(shù)修改為一個閉包
//3渺杉、為了避免資源浪費蛇数,將當(dāng)前參數(shù)修改成一個閉包
func debugOutPrint(_ condition: Bool, _ message: () -> String){
if condition {
print("debug: \(message())")
}
}
func doSomething() -> String{
print("doSomething")
return "Network Error Occured"
}
debugOutPrint(false, doSomething)
修改后,當(dāng)傳入為false
時是越,doSomething
函數(shù)不會執(zhí)行
- 如果此時傳入一個
string
耳舅,又需要如何處理呢?
可以通過@autoclosure
將當(dāng)前的閉包聲明成一個自動閉包
,不接收任何參數(shù)浦徊,返回值是當(dāng)前內(nèi)部表達式的值
馏予。所以當(dāng)傳入一個String時,其實就是將String放入一個閉包表達式中盔性,在調(diào)用的時候返回
//4霞丧、將當(dāng)前參數(shù)修改成一個閉包,并使用@autoclosure聲明成一個自動閉包
func debugOutPrint(_ condition: Bool, _ message: @autoclosure () -> String){
if condition {
print("debug: \(message())")
}
}
func doSomething() -> String{
print("doSomething")
return "Network Error Occured"
}
//---使用1:傳入函數(shù)
debugOutPrint(true, doSomething())
//---使用2:傳入字符串
debugOutPrint(true, "Application Error Occured")
自動閉包就相當(dāng)于
debugOutPrint(true, "Application Error Occured")
相當(dāng)于用{}包裹傳入的對象冕香,然后返回{}內(nèi)的值
{
//表達式里的值
return "Network Error Occured"
}
總結(jié)
-
逃逸閉包
:一個接受閉包作為參數(shù)的函數(shù)蛹尝,逃逸閉包可能會在函數(shù)返回之后才被調(diào)用,即閉包逃離了函數(shù)的作用域悉尾,例如網(wǎng)絡(luò)請求突那,需要在形參前面使用@escaping
來明確閉包是允許逃逸的。- 一般用于
異步函數(shù)的回調(diào)
构眯,比如網(wǎng)絡(luò)請求 - 如果標(biāo)記為了
@escaping
愕难,必須在閉包中顯式的引用self
- 一般用于
非逃逸閉包
:一個接受閉包作為參數(shù)的函數(shù),閉包是在這個函數(shù)結(jié)束前內(nèi)被調(diào)用惫霸,即可以理解為閉包是在函數(shù)作用域結(jié)束前被調(diào)用-
為什么要區(qū)分
@escaping
和@nonescaping
猫缭?- 1、為了內(nèi)存管理壹店,
閉包會強引用它捕獲的所有對象
猜丹,這樣閉包會持有當(dāng)前對象,容易導(dǎo)致循環(huán)引用
- 2茫打、
非逃逸閉包不會產(chǎn)生循環(huán)引用
居触,它會在函數(shù)作用域內(nèi)使用妖混,編譯器可以保證在函數(shù)結(jié)束時閉包會釋放它捕獲的所有對象 - 3老赤、
使用非逃逸閉包可以使編譯器應(yīng)用更多強有力的性能優(yōu)化
,例如制市,當(dāng)明確了一個閉包的生命周期的話抬旺,就可以省去一些保留(retain)和釋放(release)的調(diào)用 - 4、
非逃逸閉包
它的上下文的內(nèi)存可以保存在棧上
而不是堆上
- 1、為了內(nèi)存管理壹店,
總結(jié):如果沒有特別需要祥楣,
開發(fā)中使用非逃逸閉包是有利于內(nèi)存優(yōu)化
的开财,所以蘋果把閉包區(qū)分為兩種,特殊情況時再使用逃逸閉包