Swift基礎(chǔ)6(閉包)

閉包是可以在你的代碼中被傳遞和引用的功能性獨(dú)立代碼塊。

閉包能夠捕獲和存儲(chǔ)定義在其上下文中的任何常量和變量的引用,這也就是所謂的閉合并包裹那些常量和變量,因此被稱為“閉包”,Swift 能夠?yàn)槟闾幚硭嘘P(guān)于捕獲的內(nèi)存管理的操作。

閉包表達(dá)式

閉包表達(dá)式語(yǔ)法有如下的一般形式:

{ (parameters) -> (return type) in
    statements
}

閉包表達(dá)式語(yǔ)法能夠使用常量形式參數(shù)、變量形式參數(shù)和輸入輸出形式參數(shù),但不能提供默認(rèn)值??勺冃问絽?shù)也能使用,但需要在形式參數(shù)列表的最后面使用。元組也可被用來作為形式參數(shù)和返回類型。

在swift中,可以使用func定義一個(gè)函數(shù),也可以通過閉包表達(dá)式定義一個(gè)函數(shù)

//函數(shù)
func sum(_ v1:Int,_ v2:Int) -> Int {
    v1 + v2
}
//閉包表達(dá)式
var fn = {
    (v1:Int,v2:Int) in
    return v1 + v2
}
 
print(sum(1, 2)) //3
print(fn(1,2))     //3

閉包表達(dá)式的簡(jiǎn)寫

第一種寫法
Swift 的標(biāo)準(zhǔn)庫(kù)提供了一個(gè)叫做sorted(by:)的方法,會(huì)根據(jù)你提供的排序閉包將已知類型的數(shù)組的值進(jìn)行排序。一旦它排序完成, sorted(by:) 方法會(huì)返回與原數(shù)組類型大小完全相同的一個(gè)新數(shù)組,該數(shù)組的元素是已排序好的。原始數(shù)組不會(huì)被 sorted(by:)方法修改。

let names = ["Chris","Alex","Ewa","Barry","Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
print(reversedNames) //["Ewa", "Daniella", "Chris", "Barry", "Alex"]

我們這里來使用一個(gè)閉包表達(dá)式

reversedNames = names.sorted(by: {(s1:String,s2:String) -> Bool in
    return s1 > s2
})

第二種寫法:從語(yǔ)境中推斷類型

由于排序閉包為實(shí)際參數(shù)來傳遞給方法,Swift 就能推斷它的形式參數(shù)類型和返回類型。 sorted(by:)方法是在字符串?dāng)?shù)組上調(diào)用的,所以它的形式參數(shù)必須是一個(gè)(String, String) -> Bool 類型的函數(shù)。這意味著 (String, String)Bool類型不需要寫成閉包表達(dá)式定義中的一部分。因?yàn)樗械念愋投寄鼙煌茢?,返回箭頭( ->)和圍繞在形式參數(shù)名周圍的括號(hào)也能被省略

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

第三種寫法:從單表達(dá)式閉包隱式返回

單表達(dá)式閉包能夠通過從它們的聲明中刪掉return關(guān)鍵字來隱式返回它們單個(gè)表達(dá)式的結(jié)果,前面的栗子可以寫作:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

第四種寫法:簡(jiǎn)寫的實(shí)際參數(shù)名

Swift 自動(dòng)對(duì)行內(nèi)閉包提供簡(jiǎn)寫實(shí)際參數(shù)名,你也可以通過 0 ,1 , $2 等名字來引用閉包的實(shí)際參數(shù)值。

如果你在閉包表達(dá)式中使用這些簡(jiǎn)寫實(shí)際參數(shù)名,那么你可以在閉包的實(shí)際參數(shù)列表中忽略對(duì)其的定義,并且簡(jiǎn)寫實(shí)際參數(shù)名的數(shù)字和類型將會(huì)從期望的函數(shù)類型中推斷出來。 in 關(guān)鍵字也能被省略,因?yàn)殚]包表達(dá)式完全由它的函數(shù)體組成:

reversedNames = names.sorted(by: { $0 > $1 } )

這里, 0 和1 分別是閉包的第一個(gè)和第二個(gè) String 實(shí)際參數(shù)。

第五種:運(yùn)算符函數(shù)

實(shí)際上還有一種更簡(jiǎn)短的方式來撰寫上述閉包表達(dá)式。Swift 的 String 類型定義了關(guān)于大于號(hào)( >)的特定字符串實(shí)現(xiàn),讓其作為一個(gè)有兩個(gè) String 類型形式參數(shù)的函數(shù)并返回一個(gè) Bool 類型的值。這正好與 sorted(by:) 方法的第二個(gè)形式參數(shù)需要的函數(shù)相匹配。因此,你能簡(jiǎn)單地傳遞一個(gè)大于號(hào),并且 Swift 將推斷你想使用大于號(hào)特殊字符串函數(shù)實(shí)現(xiàn):

reversedNames = names.sorted(by: >)

尾隨閉包

  • 如果你需要將一個(gè)很長(zhǎng)的閉包表達(dá)式作為函數(shù)最后一個(gè)實(shí)際參數(shù)傳遞給函數(shù),使用尾隨閉包將增強(qiáng)函數(shù)的可讀性。
  • 尾隨閉包是一個(gè)被書寫在函數(shù)形式參數(shù)的括號(hào)外面(后面)的閉包表達(dá)式
func exec(v1:Int,v2:Int,fn:(Int,Int) -> Int) {
    print(fn(v1,v2))
}
 
//調(diào)用
exec(v1: 10, v2: 20){
    $0 + $1
}

如果閉包表達(dá)式作為函數(shù)的唯一實(shí)際參數(shù)傳入,而你又使用了尾隨閉包的語(yǔ)法,那你就不需要在函數(shù)名后邊寫圓括號(hào)了

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

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

閉包

一個(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
}

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

對(duì)于這樣的函數(shù),打印結(jié)果值是什么呢?我們嘗試打印一下1 3 6 10,有沒有超出我們的預(yù)料。

捕獲值

一個(gè)閉包能夠從上下文捕獲已被定義的常量和變量。即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍能夠在其函數(shù)體內(nèi)引用和修改這些值。

想要了解上面的本質(zhì),我們可以簡(jiǎn)單的看一下匯編代碼

typealias Fn = (Int) -> Int
func getFn() -> Fn {

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

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

return plus打一個(gè)斷點(diǎn),想要看匯編代碼工具欄 --> Debug --> Debug Workflow --> Always Show Disassembly

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))

然后我們?cè)诳匆幌逻@個(gè)匯編

swift_allocObject會(huì)在堆中開辟一段新的空間。

理解

我們可以把閉包想象成一個(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 cs = Closure()
cs.plus(1)
cs.plus(2)
cs.plus(3)
cs.plus(4)

【注意】
1、作為一種優(yōu)化,如果一個(gè)值沒有改變或者在閉包的外面,Swift 可能會(huì)使用這個(gè)值的拷貝而不是捕獲。
2、如果你分配了一個(gè)閉包給類實(shí)例的屬性,并且閉包通過引用該實(shí)例或者它的成員來捕獲實(shí)例,你將在閉包和實(shí)例間建立一個(gè)強(qiáng)引用環(huán)。
3、閉包是引用類型

練習(xí)2

typealias Fn = (Int) -> (Int,Int)
func getFn() -> (Fn,Fn) {

    var num1 = 0
    var num2 = 0
    
    func plus (_ i:Int) -> (Int,Int){
        num1 += i
        num2 += i * 2
        return (num1,num2)
    }
    
    func minus (_ i:Int) -> (Int,Int){
        num1 -= i
        num2 -= i * 2
        return (num1,num2)
    }
    
    return (plus,minus)
}

let (p,m) = getFn()
print(p(5))   //(5, 10)
print(m(4))  //(1, 2)
print(p(3))   //(4, 8)
print(m(2))  //(2, 4)

在一個(gè)函數(shù)里面,閉包捕獲值的地址是相同的

我是使用類對(duì)比

class Closure {
    var num1 = 0
    var num2 = 0
    
    func plus (_ i:Int) -> (Int,Int){
        num1 += i
        num2 += i * 2
        return (num1,num2)
    }
    
    func minus (_ i:Int) -> (Int,Int){
        num1 -= i
        num2 -= i * 2
        return (num1,num2)
    }
}

var cs = Closure()
print(cs.plus(5))     //(5, 10)
print(cs.minus(4))  //(1, 2)
print(cs.plus(3))     //(4, 8)
print(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

對(duì)比類

class Closure {
    var i:Int
    init(_ i:Int) {
        self.i = i
    }
    
    func get() -> Int {
        return i
    }
}

var cs:[Closure] = []
for i in 1...3{
    cs.append(Closure(i))
}

for cls in cs{
    print(cls.get())
}

逃逸閉包(@escaping )與非逃逸閉包(@noescaping)

逃逸閉包(@escaping )

當(dāng)閉包作為一個(gè)實(shí)際參數(shù)傳遞給一個(gè)函數(shù)的時(shí)候,我們就說這個(gè)閉包逃逸了,因?yàn)樗窃诤瘮?shù)返回之后調(diào)用的。當(dāng)你聲明一個(gè)接受閉包作為形式參數(shù)的函數(shù)時(shí),你可以在形式參數(shù)前寫 @escaping 來明確閉包是允許逃逸的。

閉包可以逃逸的一種方法是被儲(chǔ)存在定義于函數(shù)外的變量里。比如說,很多函數(shù)接收閉包實(shí)際參數(shù)來作為啟動(dòng)異步任務(wù)的回調(diào)。函數(shù)在啟動(dòng)任務(wù)后返回,但是閉包要直到任務(wù)完成——閉包需要逃逸,以便于稍后調(diào)用

例如:當(dāng)網(wǎng)絡(luò)請(qǐng)求結(jié)束后調(diào)用的閉包。發(fā)起請(qǐng)求后過了一段時(shí)間后這個(gè)閉包才執(zhí)行,并不一定是在函數(shù)作用域內(nèi)執(zhí)行的

override func viewDidLoad() {
        super.viewDidLoad()
         
        getData { (data) in
            print("閉包返回結(jié)果:\(data)")
        }
    }

    func getData(closure:@escaping (Any) -> Void) {
        print("函數(shù)開始執(zhí)行--\(Thread.current)")
        DispatchQueue.global().async {
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
                print("執(zhí)行了閉包---\(Thread.current)")
                closure("345")
            })
        }
        print("函數(shù)執(zhí)行結(jié)束---\(Thread.current)")
    }

從結(jié)果可以看出,逃逸閉包的生命周期是長(zhǎng)于函數(shù)的。

逃逸閉包的生命周期:

  • 1、閉包作為參數(shù)傳遞給函數(shù);
  • 2、退出函數(shù);
  • 3、閉包被調(diào)用,閉包生命周期結(jié)束

即逃逸閉包的生命周期長(zhǎng)于函數(shù),函數(shù)退出的時(shí)候,逃逸閉包的引用仍被其他對(duì)象持有,不會(huì)在函數(shù)結(jié)束時(shí)釋放。

非逃逸閉包(@noescaping)

一個(gè)接受閉包作為參數(shù)的函數(shù), 閉包是在這個(gè)函數(shù)結(jié)束前內(nèi)被調(diào)用。

    override func viewDidLoad() {
        super.viewDidLoad()
         
        handleData { (data) in
            print("閉包返回結(jié)果:\(data)")
        }
    }

    func handleData(closure:(Any) -> Void) {
        print("函數(shù)開始執(zhí)行--\(Thread.current)")
        print("執(zhí)行了閉包---\(Thread.current)")
        closure("123")
        print("函數(shù)執(zhí)行結(jié)束---\(Thread.current)")
    }

為什么要分逃逸閉包和非逃逸閉包

為了管理內(nèi)存,閉包會(huì)強(qiáng)引用它捕獲的所有對(duì)象,比如你在閉包中訪問了當(dāng)前控制器的屬性、函數(shù),編譯器會(huì)要求你在閉包中顯示 self 的引用,這樣閉包會(huì)持有當(dāng)前對(duì)象,容易導(dǎo)致循環(huán)引用。

非逃逸閉包不會(huì)產(chǎn)生循環(huán)引用,它會(huì)在函數(shù)作用域內(nèi)釋放,編譯器可以保證在函數(shù)結(jié)束時(shí)閉包會(huì)釋放它捕獲的所有對(duì)象;使用非逃逸閉包的另一個(gè)好處是編譯器可以應(yīng)用更多強(qiáng)有力的性能優(yōu)化,例如,當(dāng)明確了一個(gè)閉包的生命周期的話,就可以省去一些保留(retain)和釋放(release)的調(diào)用;此外非逃逸閉包它的上下文的內(nèi)存可以保存在棧上而不是堆上。

自動(dòng)閉包

自動(dòng)閉包是一種自動(dòng)創(chuàng)建的用來把作為實(shí)際參數(shù)傳遞給函數(shù)的表達(dá)式打包的閉包。它不接受任何實(shí)際參數(shù),并且當(dāng)它被調(diào)用時(shí),它會(huì)返回內(nèi)部打包的表達(dá)式的值

這個(gè)語(yǔ)法的好處在于通過寫普通表達(dá)式代替顯式閉包而使你省略包圍函數(shù)形式參數(shù)的括號(hào)。

func getFirstPositive1(_ v1:Int, _ v2:Int) -> Int {
    return v1 > 0 ? v1 : v2
}
getFirstPositive1(1, 2)


func getFirstPositive2(_ v1:Int, _ v2:() -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive2(1, 2) //這個(gè)報(bào)錯(cuò)
getFirstPositive2(1, {2})

func getFirstPositive3(_ v1:Int, _ v2:@autoclosure () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive3(1, 2)
  • @autoclosure會(huì)自動(dòng)的將2封裝為{2}
  • @autoclosure只支持() -> T的格式參數(shù)
  • 其中??就是一個(gè)@autoclosure
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容