07-閉包

閉包表達(dá)式(Closure Expression)

  • 在Swift中诉植,可以通過func定義一個(gè)函數(shù)嵌莉,也可以通過閉包表達(dá)式定義一個(gè)函數(shù)

  • func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }

var fn = {
 (v1: Int, v2: Int) -> Int in 
 return v1 + v2  
}
fn(10, 20) 
{ 
   (v1: Int, v2: Int) -> Int in 
   return v1 + v2  
}(10, 20)  
 {
     (參數(shù)列表) -> 返回值類型 in 
     函數(shù)體代碼  
 } 

閉包表達(dá)式的縮寫

func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))                                         
} 
exec(v1: 10, v2: 20, fn: {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2                                                             
})
exec(v1: 10, v2: 20, fn: {
    v1, v2 in return v1 + v2                                                         
})
exec(v1: 10, v2: 20, fn: {
    v1, v2 in v1 + v2                                                          
})
exec(v1: 10, v2: 20, fn: { $0 + $1 })
exec(v1: 10, v2: 20, fn: +)

尾隨閉包

  • 如果將一個(gè)很長的閉包表達(dá)式作為函數(shù)的最后一個(gè)實(shí)參,使用尾隨閉包可以增強(qiáng)函數(shù)的可讀性

  • 尾隨閉包是一個(gè)被書寫在函數(shù)調(diào)用括號(hào)外面(后面)的閉包表達(dá)式

func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))                                                         
}

exec(v1: 10, v2: 20) {
    $0 + $1                                                     
}
  • 如果閉包表達(dá)式是函數(shù)的唯一實(shí)參苟鸯,而且使用了尾隨閉包的語法,那就不需要在函數(shù)名后邊寫圓括號(hào)
func exec(fn: (Int, Int) -> Int) {
    print(fn(1, 2))                                                           
}

exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }

示例 - 數(shù)組的排序

func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
/// 返回true: i1排在i2前面
/// 返回false: i1排在i2后面
func cmp(i1: Int, i2: Int) -> Bool {                                                               
// 大的排在前面
    return i1 > i2
}
var nums = [11, 2, 18, 6, 5, 68, 45]
nums.sort(by: cmp)
// [68, 45, 18, 11, 6, 5, 2]
nums.sort(by: {
    (i1: Int, i2: Int) -> Bool in
    return i1 < i2
})
nums.sort(by: { i1, i2 in return i1 < i2 })
nums.sort(by: { i1, i2 in i1 < i2 })
nums.sort(by: { $0 < $1 })
nums.sort(by: <)
nums.sort() { $0 < $1 }
nums.sort { $0 < $1 }
// [2, 5, 6, 11, 18, 45, 68]

忽略參數(shù)

func exec(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}

exec {_,_ in 10} // 10

閉包 (Closure)

  • 網(wǎng)上有各種關(guān)于閉包的定義,個(gè)人覺得比較嚴(yán)謹(jǐn)?shù)亩x是

    • 一個(gè)函數(shù)和它所捕獲的變量\常量環(huán)境組合起來纪隙,稱為閉包

    • 一般指定義在函數(shù)內(nèi)部的函數(shù)

    • 一般它捕獲的是外層函數(shù)的局部變量\常量

typealias Fn = (Int) -> Int
func getFn() -> Fn {
    var num = 0 // 局部變量
    func plus(_ i: Int) -> Int {                                
        num += i                                                        
        return num
    }                                                           
    return plus
} // 返回的plus和num形成了閉包
var fn1 = getFn()
var fn2 = getFn()
fn1(1) // 1
fn2(2) // 2
fn1(3) // 4
fn2(4) // 6
fn1(5) // 9
fn2(6) // 12

// 2分堆空間,后面推導(dǎo)過程

思考:如果num是全局變量呢?

func getFn() -> Fn {
    var num = 0
    return {
        num += $0                                                                
        return num
    }
}
  • 可以把閉包想象成是一個(gè)類的實(shí)例對(duì)象

  • 內(nèi)存在堆空間

  • 捕獲的局部變量\常量就是對(duì)象的成員(存儲(chǔ)屬性)

  • 組成閉包的函數(shù)就是類內(nèi)部定義的方法

class Closure {
    var num = 0
    func plus(_ i: Int) -> Int {
        num += i                                                           
        return num
    }
}

var cs1 = Closure()
var cs2 = Closure()
cs1.plus(1) // 1
cs2.plus(2) // 2
cs1.plus(3) // 4
cs2.plus(4) // 6
cs1.plus(5) // 9
cs2.plus(6) // 12
推導(dǎo)過程:
var num = 0 // 全局變量
typealias Fn = (Int) -> Int
func getFn() -> Fn {
    func plus(_ i: Int) -> Int {
        num += i
        return num
    }
    return plus
} // 返回的plus和num形成了閉包

var fn = getFn()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))

/*
 1
 3
 6
 10
*/
如果是全局變量的話扛或,訪問的都是同一塊內(nèi)存绵咱。輸出值如上
換成局部變量
  • 結(jié)果和全局變量一樣,為什么局部變量沒有銷毀呢熙兔?
typealias Fn = (Int) -> Int
func getFn() -> Fn {
    var num = 0 // 局部變量
    func plus(_ i: Int) -> Int {
        // num += i
        //return num
        return i
    }
    return plus
} 

var fn = getFn()

  • 先查看一下上面這份代碼的匯編
image.png
leaq 0xd(%rip), %rax  // rip+0xd 算出來的地址悲伶,直接給rax                        找房間號(hào)
movq 0xd(%rip), %rax  // rip+0xd 算出來的地址所在的存儲(chǔ)空間,取出8個(gè)字節(jié)住涉,賦值給rax 去房間號(hào)里找人
  • 接下來看下用到局部變量后的匯編代碼
typealias Fn = (Int) -> Int
func getFn() -> Fn {
    var num = 0 // 局部變量
    func plus(_ i: Int) -> Int {
        num += i
        return num
    }
    return plus
}

var fn = getFn()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
/*
 1
 3
 6
 10
*/
image.png

推斷堆空間存儲(chǔ)了num麸锉,保活了num舆声,訪問的同一個(gè)內(nèi)存空間

匯編 movq $0, 0x8(%rax)

image.png

TODO 匯編分析閉包本質(zhì)花沉,7-1

練習(xí) 1


typealias Fn = (Int) -> (Int, Int) 
func getFns() -> (Fn, Fn) {                                                          
   var num1 = 0
   var num2 = 0
   func plus(_ i: Int) -> (Int, Int) { 
        num1 += i
        num2 += i << 1
        return (num1, num2)                                                   
   }
   func minus(_ i: Int) -> (Int, Int) { 
        num1 -= i
        num2 -= i << 1
        return (num1, num2)                                                 
    } 
    return (plus, minus)
}
let (p, m) = getFns()
p(5) // (5, 10)
m(4) // (1, 2)
p(3) // (4, 8)

m(2) // (2, 4) 
class Closure {
    var num1 = 0
    var num2 = 0                                                   
    func plus(_ i: Int) -> (Int, Int) {
        num1 += i                                                     
        num2 += i << 1 
        return (num1, num2)
    }

    func minus(_ i: Int) -> (Int, Int) {
        num1 -= i                                                      
        num2 -= i << 1 
        return (num1, num2)
    }                                                 
} 

// plus和minus共享一份
var cs = Closure()
cs.plus(5)  // (5, 10)
cs.minus(4) // (1, 2)
cs.plus(3)  // (4, 8)
cs.minus(2) // (2, 4)

練習(xí) 2


var functions: [() -> Int] = []
for i in 1...3 {
    functions.append { i }
}

for f in functions {
    print(f())                                                  
}

// 1
// 2
// 3
class Closure {
    var i: Int
    init(_ i: Int) {
        self.i = i
    }
    func get() -> Int {                                                                
        return i
    }
}
var clses: [Closure] = []
for i in 1...3 {
    clses.append(Closure(i))
}
for cls in clses {
    print(cls.get())                                                            
}

注意

  • 如果返回值是函數(shù)類型,那么參數(shù)的修飾要保持統(tǒng)一
func add(_ num: Int) -> (inout Int) -> Void {
    func plus(v: inout Int) {                                                            
        v += num
    }                                                            
    return plus
}

var num = 5
add(20)(&num)
print(num)

自動(dòng)閉包

@autoclosure 會(huì)自動(dòng)將 20 封裝成閉包 { 20 }
@autoclosure 只支持 () -> T 格式的參數(shù)
@autoclosure 并非只支持最后1個(gè)參數(shù)
空合并運(yùn)算符 ?? 使用了 @autoclosure 技術(shù)
有@autoclosure媳握、無@autoclosure碱屁,構(gòu)成了函數(shù)重載
// 如果第1個(gè)數(shù)大于0,返回第一個(gè)數(shù)蛾找。否則返回第2個(gè)數(shù)
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {                                                           
    return v1 > 0 ? v1 : v2
}

getFirstPositive(10, 20) // 10
getFirstPositive(-2, 20) // 20
getFirstPositive(0, -4)  // -4
// 改成函數(shù)類型的參數(shù)娩脾,可以讓v2延遲加載
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? {     
    return v1 > 0 ? v1 : v2()
}

getFirstPositive(-4) { 20 }
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
    return v1 > 0 ? v1 : v2()                                                                
}

getFirstPositive(-4, 20)
  • 為了避免與期望沖突,使用了@autoclosure的地方最好明確注釋清楚:這個(gè)值會(huì)被推遲執(zhí)行
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腋粥,一起剝皮案震驚了整個(gè)濱河市晦雨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌隘冲,老刑警劉巖闹瞧,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異展辞,居然都是意外死亡奥邮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洽腺,“玉大人脚粟,你說我怎么就攤上這事≌号螅” “怎么了核无?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長藕坯。 經(jīng)常有香客問我团南,道長,這世上最難降的妖魔是什么炼彪? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任吐根,我火速辦了婚禮,結(jié)果婚禮上辐马,老公的妹妹穿的比我還像新娘拷橘。我一直安慰自己,他們只是感情好喜爷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布冗疮。 她就那樣靜靜地躺著,像睡著了一般檩帐。 火紅的嫁衣襯著肌膚如雪赌厅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天轿塔,我揣著相機(jī)與錄音,去河邊找鬼仲墨。 笑死勾缭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的目养。 我是一名探鬼主播俩由,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼癌蚁!你這毒婦竟也來了幻梯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤努释,失蹤者是張志新(化名)和其女友劉穎碘梢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伐蒂,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煞躬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恩沛。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡在扰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雷客,到底是詐尸還是另有隱情芒珠,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布搅裙,位于F島的核電站皱卓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏呈宇。R本人自食惡果不足惜好爬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甥啄。 院中可真熱鬧存炮,春花似錦、人聲如沸蜈漓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽融虽。三九已至享完,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間有额,已是汗流浹背般又。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巍佑,地道東北人茴迁。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像萤衰,于是被迫代替她去往敵國和親堕义。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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