天天看點

14.Swift學習之閉包

閉包引入

計算1個數的平方

  • 函數寫法
func square(param:Int)  -> Int{  
    return param * param
}
square(param:3)
           
  • 閉包寫法
let squareCloure = { (param:Int) -> Int in
    return param * param
}
squareCloure(3)
           

閉包含義

  • 閉包是可以被傳遞和引用的一個獨立子產品
  • 閉包能夠捕獲和存儲定義在其上下文中的任何常量和變量,即

    閉合并包裹那些常量和變量

    ,是以被稱為“閉包”
  • 閉包符合如下三種形式中的一種:
    • 全局函數是一個有名字但不會捕獲任何值的閉包
    • 内嵌函數是一個有名字且能從其上層函數捕獲值的閉包(函數中的嵌套函數知識點)
    • 閉包表達式是一個輕量級文法,可以捕獲其上下文中常量或變量值的沒有名字的閉包
  • 閉包和函數一樣也是引用類型

簡單案例

  • 案例一
let demo= { print("Swift 閉包執行個體。") }
demo()
           
  • 案例二
let divide = {(val1: Int, val2: Int) -> Int in 
   return val1 / val2 
}
let result = divide(200, 20)
print (result)
           

閉包表達式

閉包表達式文法有如下的一般形式:

{ (parameters) -> (return type) in
    statements
  }
           
  • 閉包表達式由一對

    {}

    開始與結束
  • in

    關鍵字将閉包分割成兩部分:

    參數與傳回值

    閉包體

  • 閉包參數與函數參數的差別
    • 形式參數不能提供預設值,其他和函數一樣

閉包主要知識點

參數名稱縮寫
  • Swift 提供了參數名稱的縮寫功能,直接通過

    $0,$1,$2

    來順序調用閉包的參數
  • 在閉包表達式中使用參數名稱縮寫,可以在閉包參數清單中省略對其定義
    • 參數類型可以通過函數類型進行推斷
    • 在單行閉包的時候,return 關鍵字可以省略
    • 參數名稱省略以後,in 關鍵字也可以被省略
//從數組中篩選指出合适的資料組成新的數組
func getList(score:[Int], con:(Int)->Bool) -> [Int]{
    
    var newScore:[Int] = [Int]()
    for item in score {
        if con(item) {
            newScore.append(item)
        }
    }
    return newScore
}

let newAarray = getList(score: [75,60,95,45,85],  con:{(s:Int)->Bool in return s>80})
print(newAarray)
           

第一種簡寫: 省略

->

與傳回類型(根據後面表達式可以推斷傳回值是一個Bool)

let newAarray = getList(score: [75,60,95,45,85],  con:{(s:Int) in return s>80})
           

第二種簡寫:省略參數類型和括号(根據函數的參數可推斷傳進來的必然是Int)

let newAarray = getList(score: [75,60,95,45,85],  con:{s in return s>80})
           

第三種簡寫:省略

return

關鍵字

let newAarray = getList(score: [75,60,95,45,85],  con:{s in s>80})
           

第四種簡寫:參數名稱縮寫,省略參數聲明和

in

,改為$0

let newAarray = getList(score: [75,60,95,45,85],  con:{$0>80})
           
捕獲
  • 閉包可以從上下文環境中捕獲常量、變量,并在自己的作用域内使用
  • Swift最簡單的閉包形式是嵌套函數,也就是定義在其他函數的函數體内的函數,嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變量。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

let incrementByTen = makeIncrementor(forIncrement: 10)
// 傳回的值為10
print(incrementByTen())
// 傳回的值為20
print(incrementByTen())
// 傳回的值為30
print(incrementByTen())
           
尾随閉包
  • 尾随閉包是一個書寫在函數括号之後的閉包表達式,函數支援将其作為最後一個參數調用
    • 閉包是函數的最後一個參數
    • 調用時,函數的

      )

      可以前置到倒數第二個參數末尾,後面的參數直接使用

      { // 執行代碼 }

      ,形式參數标簽也随之省略
    • 将一個很長的閉包表達式作為最後一個參數傳遞給函數,可以使用尾随閉包來增強函數的可讀性
//尾随閉包
func doSomething(info:String, clousre:(String)->Void){  
     clousre(info)
}

//不使用尾随閉包進行函數調用
doSomething(info: "Hello", clousre: { s in print(s) })

//使用尾随閉包進行函數調用
doSomething(info: "World") { s in
    print(s)
}
           
逃逸閉包
  • 閉包作為一個參數傳遞給一個函數
  • 傳入函數的閉包如果在函數執行結束之後才會被調用,那麼這個閉包就叫做逃逸閉包
  • 聲明一個接受閉包作為形式參數的函數時,可以在形式參數的類型之前寫上

    @escaping

    來明确閉包是允許逃逸的
  • 逃逸閉包會在函數結束後才執行

  • 舉例
//逃逸閉包:閉包可以超出函數的範圍來調用
//存放沒有參數、沒有傳回值的閉包
var closureArray :[()->Void] = [()->Void]()
//定義一個函數,接收一個非逃逸閉包為參數
func nonEscapeClosure(closure:()->Void){  
    closure()
}
//定義一個函數,接收一個逃逸閉包為參數,将閉包并存儲到一個數組裡面去,并沒有調用
func escapeClosure(closure: @escaping ()->Void){
    print("函數開始")
    closureArray.append(closure)
    print("函數結束")
}
var x = 10
//列印10
print(x)

nonEscapeClosure {
    x = 100
}
//列印100 因為閉包在函數裡面執行了
print(x)

escapeClosure {
    x = 200
}
//列印100 因為閉包逃逸了 沒有在函數裡面執行
print(x)

closureArray.first?()
//列印200 在函數外面調用了閉包
print(x)

//尾随閉包常用于異步回調
           
自動閉包
  • 一種自動建立的閉包,用于包裝函數參數的表達式
  • 不接受任何參數,被調用時會傳回被包裝在其中的表達式的值
  • 在形式參數的類型之前加上

    @autoclosure

    關鍵字辨別這是一個逃逸閉包
//自動閉包
func printIfTrue(predicate:@autoclosure ()->Bool){  
    if predicate() {
        print("is true")   
    }    
    else{       
        print("is false")       
    }   
}

//直接進行調用了,Swift 将會把 2 > 1 這個表達式自動轉換為 () -> Bool。這樣我們就得到了一個寫法簡單、表意清楚的表達式。
printIfTrue(predicate: 2>1)
printIfTrue(predicate: 2<1)
           

閉包的循環引用

class NetworkTools: NSObject {
/// 完成回調屬性
var finishedCallBack: (()->())?
/// 加載資料
/// - parameter finished: 完成回調
func loadData(finished: () -> ()) {

    self.finishedCallBack = finished
    working()
}

func working() {
    finishedCallBack?()
}

deinit {
    print("網絡工具 88")
}

           
class ViewController: UIViewController {

    var tools: NetworkTools?
    override func viewDidLoad() {
        super.viewDidLoad()

        tools = NetworkTools()
        tools?.loadData() {
           print("加載資料完成,更新界面:", NSThread.currentThread())
           weakSelf!.view.backgroundColor = UIColor.redColor()
        }
    }
    /// 與 OC 中的 dealloc 類似,注意此函數沒有()
    deinit {
        print("控制器 88")
    }
}

           
Swift中解決循環引用的方式
  • 方案一:
    • 使用weak,對目前控制器使用弱引用
    • 但是因為self可能有值也可能沒有值,是以weakSelf是一個可選類型,在真正使用時可以對其強制解包(該處強制解包沒有問題,因為控制器一定存在,否則無法調用所在函數)
// 解決方案一:
weak var weakSelf = self
tools.loadData {
    print("加載資料完成,更新界面:", NSThread.currentThread())
    weakSelf!.view.backgroundColor = UIColor.redColor()
}
           
  • 方案二:
  • 和方案一類型,隻是書寫方式更加簡單
  • 可以寫在閉包中,并且在閉包中用到的self都是弱引用
tools.loadData {[weak self] () -> () in
    print("加載資料完成,更新界面:", NSThread.currentThread())
    self!.view.backgroundColor = UIColor.redColor()
}
           
  • 方案三:
    • 使用關鍵字unowned
    • 從行為上來說 unowned 更像OC中的 unsafe_unretained
    • unowned 表示:即使它原來引用的對象被釋放了,仍然會保持對被已經釋放了的對象的一個 "無效的" 引用,它不能是 Optional 值,也不會被指向 nil
tools.loadData {[unowned self] () -> () in
    print("加載資料完成,更新界面:", NSThread.currentThread())
    self.view.backgroundColor = UIColor.redColor()
}