Swift閉包簡單應用

語法表達式


{ (parameters) -> returnType in
    statements
}複製代碼

這裏的參數(parameters),能夠是in-out(輸入輸出參數),但不能設定默認值。若是是可變參數,必須放在最後一位,否則編譯器報錯。元組也能夠做爲參數或者返回值。
"in"關鍵字表示閉包的參數和返回值類型定義已經完成,閉包函數體即將開始。即由in引入函數。api

閉包有三種形式:安全

  1. 全局函數是具備名稱而且不捕獲任何值得閉包
  2. 嵌套函數是具備名稱並能夠從其閉包函數捕獲值得閉包
  3. 閉包表達式是以輕量級語法編寫的未命名的閉包,能夠從其定義的上下文中捕獲並存儲任何常量和變量的引用。

閉包表達式的優勢:bash

  1. 從上下文中推導參數和返回值類型
  2. 單個表達式的閉包會隱式返回
  3. 速記參數名稱
  4. 尾隨閉包語法

簡單實例:閉包

let sum = {(a: Int, b: Int) -> Int in
    return a + b
}
let s = sum(10, 15)
print(s)

// 從上下文中推導參數和返回值類型
let sum1 = { (a, b) -> Int in
    return a + b
}
print(sum1(25, 25))複製代碼

概括
閉包類型是由參數類型和返回值類型決定的,和函數是同樣的。閉包的類型是(parameters) -> returnTypeide

起別名

Swift使用typealias關鍵字能夠爲類型起一個別名函數

typealias block = (Int, Int) -> Int
let sum: block = {(a, b) in
    return a + b
}
print(sum(10, 15))複製代碼

閉包表達式的用法,以sorted方法使用爲例ui

// 尾隨閉包
let num = [1, 2, 3, 4, 5, 6, 7]
let sortNum = num.sorted(by: {
    (a: Int, b: Int) -> Bool in
    return a > b
})
print("第一次排序: \(sortNum)")

// 從上下文中推導參數和返回值類型
let sortNum1 = sortNum.sorted(by: {
    (a, b) in
    return a < b
})
print("第二次排序: \(sortNum1)")

// 單表達式的閉包隱式返回,能夠省略return關鍵字
let sortNum2 = sortNum1.sorted(by: {
    (a, b) in
    a > b
})
print("第三次次排序: \(sortNum2)")

// 速記參數名,和隱式返回。Swift提供速記參數名關聯閉包參數(須要省略參數列表,並根據預期的函數類型推斷速記參數名的數量和類型),從$0開始
let sortNum3 = sortNum2.sorted(by: {
    $0 < $1
})
print("第四次次排序: \(sortNum3)")

// sorted方法還可使用函數進行排序
func sortNumArr(a: Int, b: Int) -> Bool
{
    return a > b
}
let sortNum4 = sortNum3.sorted(by: sortNumArr)
print("第五次次排序: \(sortNum4)")

/*
輸出結果
第一次排序: [7, 6, 5, 4, 3, 2, 1]
第二次排序: [1, 2, 3, 4, 5, 6, 7]
第三次次排序: [7, 6, 5, 4, 3, 2, 1]
第四次次排序: [1, 2, 3, 4, 5, 6, 7]
第五次次排序: [7, 6, 5, 4, 3, 2, 1]
*/複製代碼

尾隨閉包


sorted的使用,也能夠將閉包寫在括號以外。例如:spa

let num = [1, 2, 3, 4, 5, 6, 7]
let sortNum = num.sorted() {
    (a: Int, b: Int) -> Bool in
    return a > b
}
print("第一次排序: \(sortNum)")複製代碼

若將閉包做爲函數參數最後一個參數,能夠省略參數標籤,將閉包表達式寫在函數調用括號後面指針

func sum(by: (Int, Int) -> Int) -> Int {
    return by(10, 20)
}
let num = sum() {
    (a, b) in
    return a + b
}
print("和爲 \(num)")


func sum1(a: Int, b: Int, by: (Int, Int) -> Int) -> Int{
    return by(a, b)
}

let num1 = sum1(a: 10, b: 30) {
    (a, b) in
    return a + b
}
print("和爲 \(num1)")複製代碼

捕獲常量和變量


閉包能夠在其定義的上下文中捕獲常量和變量。及時定義這些常量和變量的原始範圍再也不存在了,閉包也能夠引用並修改這些常量和變量的值。code

  • 嵌套函數捕獲值
func sjzFunc(a: Int) -> () -> Int {
    var sjz = 0

    func addSjz() -> Int {
        sjz += a
        return sjz
    }

    addSjz()
    print("sjz的值爲:\(sjz), a的值爲:\(a)")

    return addSjz
}

let sjzF = sjzFunc(a: 100)
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")

/*
sjz的值爲:100, a的值爲:100
值爲:200
值爲:300
值爲:400
值爲:500
*/複製代碼
  • 閉包捕獲值:
func sjzFunc(a: Int) -> () -> Int {
    var sjz = 0

    // 閉包
    let addSjz: () -> Int = {
        sjz += a
        return sjz
    }
    addSjz()
    print("sjz的值爲:\(sjz), a的值爲:\(a)")

    return addSjz
}

let sjzF = sjzFunc(a: 100)
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
/*
sjz的值爲:100, a的值爲:100
值爲:200
值爲:300
值爲:400
值爲:500
*/複製代碼

能夠看出,當調用addSjz的時候,sjz的值也改變了
addSjz捕獲對變量sjz和參數a的引用,經過引用捕獲確保sjzFunc結束時,sjz和a不會消失。而且確保了下次調用sjzF時,sjz可用。當它們再也不須要時,Swift處理變量的全部內存管理。

函數和閉包是引用類型。也就是說,當給變量和常量賦值閉包或函數時,變量和常量被賦值爲閉包或函數的引用。
例如:

let sjzFF = sjzF // sjzFF和sjzF引用的是同一個閉包複製代碼

逃避閉包


當閉包做爲參數傳遞給函數時,閉包被稱爲轉義函數,在函數返回後調用。
聲明一個將閉包做爲一個參數的函數時,能夠在參數類型以前編寫@escaping,以指示容許閉包被轉義。

閉包能夠逃避的方法之一是存儲在函數外定義的變量中。

var sjzBlock: () -> Void = {

}

func someEscapingFunc(com: @escaping () -> Void){
    print("執行函數")
    sjzBlock = com
}

func someNoneFunc(com: () -> Void){
    com()
}

class someClass {
    var x = 10
    func doSomeThint() -> Void {
        someEscapingFunc {
            self.x = 20
        }

        someNoneFunc {
            x = 30
        }
    }
}

let some = someClass()
some.doSomeThint()
print(some.x)

sjzBlock()
print(some.x)

/*
輸出
執行函數
30
20
*/複製代碼

用@escaping標記一個閉包意味着你必須在閉包中self引用。 例如,someEscapingFunc的閉包是一個轉義閉包,這意味着它須要self引用。 相反,封閉傳遞給someNoneFunc是一個非轉義的閉包,這意味着它能夠隱式引用。

@escaping和@non-escaping修飾符是用來修飾閉包的。閉包默認爲@non-escaping類型
@escaping:轉義閉包,閉包的生命週期不在傳入的函數範圍內管理。由函數外部變量持有。當函數結束以後,閉包纔去執行
@non-escaping:閉包在函數內執行完後,函數纔去執行,閉包銷燬。
當使用@escaping時,要考慮self被循環引用。

循環引用


緣由參考OC的block,解決方法也相似。

Dog類

import Cocoa

class sjzDog: NSObject {
    var eatFull: ((String) -> Void) = {(String) in

    }
    var food: String?

    override init() {
        super.init()
    }

    deinit {
        print("狗掛了")
    }

    func eat(food: String, by: @escaping (String) -> Void) -> Void {
        self.food = food
        print("我吃了:\(food)")
        eatFull = by
    }

    func eatFinish() -> Void {
        eatFull(food!)
    }
}複製代碼
People類

import Cocoa

class sjzPeople: NSObject {
    let dog: sjzDog
    var name: String?

    init(name: String) {
        self.name = name
        dog = sjzDog()

        super.init()
    }

    deinit {
        print("人掛了")
    }

    func feed(food: String) -> Void {
//        循環應用
//        輸出:
//        我吃了:milk
//        我吃了milk, 我吃飽了,我想和主人Tom玩耍
//        dog.eat(food: food) { (a) in
//            print("我吃了\(a), 我吃飽了,我想和主人\(self.name!)玩耍")
//        }


        /*如下輸出:
             我吃了:milk
             我吃了milk, 我吃飽了,我想和主人Tom玩耍
             人掛了
             狗掛了
         */
//        1. 解決循環引用
//        weak var weakSelf = self
//        dog.eat(food: food) { (a) in
//            print("我吃了\(a), 我吃飽了,我想和主人\(weakSelf!.name!)玩耍")
//        }

//        2. 解決循環引用
//        dog.eat(food: food) { [unowned self] (a) in
//            print("我吃了\(a), 我吃飽了,我想和主人\(self.name!)玩耍")
//        }

//        3. 解決循環引用
        dog.eat(food: food) { [weak self] (a) in
            print("我吃了\(a), 我吃飽了,我想和主人\(self!.name!)玩耍")
        }

        dog.eatFinish()
    }
}複製代碼
調用

var people: sjzPeople? = sjzPeople.init(name: "Tom")
people!.feed(food: "milk")
people = nil複製代碼

循環引用緣由:
dog類中引用了閉包eatFull,people類中引用了dog類,閉包eatFull又引用了people,造成了閉環

在dog類中繼續聲明變量和方法:

var play: (() -> Void)?

func playOrFood() -> Void {
        play = {
            print("我要玩, 我不想吃 \(String(describing: self!.food))")
        }

        play!()
    }複製代碼

在調用playOrFood方法後,將dog對象置空,dog對象不能釋放,造成了循環引用
緣由:
dog類引用了paly閉包,paly閉包又引用了dog類。解決方法和上面dog類和people互相引用相似

解決方法:

  1. weak var weakSelf = self,使用weakSelf調用屬性
  2. 在閉包{和參數之間使用[unowned self]
  3. 在閉包{和參數之間使用[weak self]

weak表示可用範圍內self爲弱引用unowned表示可用範圍內self都爲assign,不會強引用,若是對象釋放,指針地址依然存在,不安全

相關文章
相關標籤/搜索