Swift:閉包(Closures)

來源:互聯網
上載者:User

標籤:閉包   swift   closures   

一、 基本概念 

閉包(Closures)是自包含的功能代碼塊,可以在代碼中使用或者用來作為參數傳值。 在Swift中的閉包與C、OC中的blocks和其它程式設計語言(如C#)中的lambda, javascript中的函數嵌套等類似。 閉包可以捕獲和儲存上下文中定義的的任何常量和變數的引用。這就是所謂的變數和變數的自封閉, 因此閉包還會處理所有捕獲的引用的記憶體管理。 全域函數和嵌套函數其實就是特殊的閉包。 閉包的形式有: 

(1)全域函數都是閉包,有名字但不能捕獲任何值。 
(2)嵌套函數都是閉包,且有名字,也能捕獲封閉函數內的值。 
(3)閉包運算式都是無名閉包,使用輕量級文法,可以根據上下文環境捕獲值。 
   
Swift中的閉包有很多最佳化的地方: 
(1)根據上下文推斷參數和返回值類型 
(2)從單行運算式閉包中隱式返回(也就是閉包體只有一行代碼,可以省略return) 
(3)可以使用簡化參數名,如$0, $1(從0開始,表示第i個參數...) 

(4)提供了尾隨閉包文法(Trailing closure syntax) 


二、使用舉例(這裡所列舉的例子,均從《The Swift Programming Language》這本書總結所得)
下面用Swift標準庫中的sort方法來一步步簡化閉包寫法  
 sort函數需要兩個參數  
 參數一:數組  
 參數二:一個閉包:帶有兩個參數,這兩個參數類型與數組中的元素類型相同,返回值是Bool
數組:
var names = ["Swift", "Arial", "Soga", "Donary"]

第一種方式:使用函數
func backwards(firstString: String, secondString: String) -> Bool {     return firstString > secondString // 降序排序   }
調用:
sort(&names, backwards)

這種方式的使用相當於回調backward方法。


第二種方式:使用閉包方式 
完整閉包寫法是在花括弧內有參數列表和返回值,用關鍵字in表明閉包體的開始 
(1)  (firstString: String, secondString: String) 閉包參數列表  
(2)  -> Bool 指明閉包返回值類型是Bool  
(3)  in關鍵字表明閉包體的開始  
sort(&names, { (firstString: String, secondString: String) -> Bool in      return firstString > secondString      }) 

這裡可以進一步簡化寫法,因為閉包代碼比較短,可以寫到一行上
sort(&names, { (firstString: String, secondString: String) -> Bool in return firstString > secondString})

下面再進一步簡化寫法 :根據環境內容自動推斷出類型 ,參數列表都沒有指明類型,也沒有指明返回值類型,這是因為swift可以根據上下文推測出 ,firstString和secondString的類型會是names數組元素的類型,而返回值類型會根據return語句結果得到
sort(&names, { firstString, secondString in return firstString > secondString}) 

再進一步簡化:隱式返回(單行語句閉包), 因為閉包體只有一行代碼,可以省略return 
sort(&names, { firstString, secondString in firstString > secondString})

再進一步簡化:使用簡化參數名($i,i=0,1,2...從0開始的),Swift會推斷出閉包需要兩個參數,類型與names數組元素相同 
sort(&names, { $0 > $1 })  

最簡單的一種寫法:使用操作符
sort(&names, >)

三、 尾隨閉包(Trailing Closures)
如果函數需要一個閉包參數作為參數,且這個參數是最後一個參數,而這個閉包運算式又很長時, 使用尾隨閉包是很有用的。尾隨閉包可以放在函數參數列表外,也就是括弧外。就是將原本在參數列表內的閉包提取到函數的後邊書寫,這樣就利於閱讀及使用。
1. 還是用sort的方法為例,正常的閉包寫法如下:
sort(&names, { (firstString: String, secondString: String) -> Bool in      return firstString > secondString      }) 

但可以發現,sort函數中,第二個參數非常的長並且不利於閱讀,這時,我們可以使用尾隨閉包進行改造,代碼如下:
sort(&names){    (firstString, secondString) -> Bool in    return firstString > secondString}

2. map方法使用舉例,輸出一個數組對應的字串
var numbers = [1,2,3]let strings = numbers.map({    (var number) -> String in    var output = ""    while number > 0 {        output = String(number % 10) + output        number /= 10    }    return output})

注意到,map是一個方法,而其參數就只有一個閉包,所以我們同樣可以使用尾隨閉包的形式,寫成如下形式
var numbers = [1,2,3]let strings = numbers.map(){    (var number) -> String in    var output = ""    while number > 0 {        output = String(number % 10) + output        number /= 10    }    return output}

而map函數沒有任何其他的參數,只有一個閉包參數,所以map後面的"()"也可以省略,最終的尾隨閉包可以寫成如下形式
var numbers = [1,2,3]let strings = numbers.map{    (var number) -> String in    var output = ""    while number > 0 {        output = String(number % 10) + output        number /= 10    }    return output}

四、 捕獲值 
閉包可以根據環境內容捕獲到定義的常量和變數。閉包可以引用和修改這些捕獲到的常量和變數。在Swift中閉包的最簡單形式是嵌套函數。
func increment(#amount: Int) -> (() -> Int) {    var total = 0    func incrementAmount() -> Int {      total += amount // total是外部函數體內的變數,這裡是可以捕獲到的      return total    }    return incrementAmount // 返回的是一個嵌套函數(閉包)  }

閉包是參考型別,所以incrementByTen聲明為常量也可以修改total
let incrementByTen = increment(amount: 10)   incrementByTen() // return 10,incrementByTen是一個閉包  // 這裡是沒有改變對increment的引用,所以會儲存之前的值  incrementByTen() // return 20     incrementByTen() // return 30       let incrementByOne = increment(amount: 1)  incrementByOne() // return 1  incrementByOne() // return 2      incrementByTen() // return 40   incrementByOne() // return 3  

五、 閉包的循環參考問題
在objective-c時期,在使用block的時候不得不考慮block的循環參考問題。當時採取的措施就是將循環參考的一方弱化,比如:
__weak typeof(self) wSelf = self;

在Swift中的閉包中也存在同樣的問題,其實大致思路是一致的,我們可以使用unowned 關鍵字。 
Example, 將一個對象的屬性轉化為XML形式顯示:
class HTMLElement {    var name: String    var text: String?        init(name: String, text: String?) {        self.name = name        self.text = text    }        lazy var asHtml: () -> String = {        [unowned self] in        if let text = self.text {            return "<\(self.name)>\(self.text)</\(self.name)>"        } else {            return "<\(self.name)>"        }    }}var html = HTMLElement(name: "Node", text: "Jack")html.asHtml()

上面的代碼中,self對asHtml這個閉包屬性有強引用,而asHtml內部又有對self的強引用,所以我們可以使用[unowned self] 將self “弱化”。從而解除了循環參考。


六、 閉包在UIKit實際環境中的使用舉例
UI介面如下:


功能很簡單
1. 上麵灰色的View就是自訂的View(TestView.swift),它包含一個文本輸入框和一個“GO”按鈕。
2. 後面這一塊就是控制器的View(ViewController.swift),它裡麵包含一個testLabel。
3. 點擊"GO"按鈕後將文字框中的內容回調到ViewController.swift中的Label顯示
TestView.swift 代碼:
class TestView: UIView {    private weak var textField1: UITextField!        var testClosure: ((str1: String) -> Void)?        override init(frame: CGRect) {        super.init(frame: frame)                var size = UIScreen.mainScreen().bounds.size        self.frame = CGRectMake(0, 0, size.width, kViewHeight)        self.backgroundColor = UIColor.grayColor()                var textField1 = UITextField(frame: CGRectMake(10, 30, 150, 30))        textField1.backgroundColor = UIColor.whiteColor()        self.addSubview(textField1)        self.textField1 = textField1                var btn = UIButton(frame: CGRectMake(300, 30, 50, 30))        btn.setTitle("GO", forState: UIControlState.Normal)        btn.addTarget(self, action: "showResult", forControlEvents: UIControlEvents.TouchDown)        self.addSubview(btn)    }        func showResult() {        self.testClosure?(str1: self.textField1.text)    }        required init(coder aDecoder: NSCoder) {        fatalError("init(coder:) has not been implemented")    }}

代碼中,我定義了一個testClosure閉包屬性,當點擊“GO”按鈕則會調用其方法。注意到閉包的定義
var testClosure: ((str1: String) -> Void)?
它是可空的,因為在控制器ViewController.swift中不一定完成了對testClosure閉包屬性賦值的工作。所以在調用閉包的時候寫成
self.testClosure?(str1: self.textField1.text)
而在ViewController.swift賦值的代碼如下:
class ViewController: UIViewController {        @IBOutlet weak var testLabel: UILabel!    override func viewDidLoad() {        super.viewDidLoad()                var tab = TestView()        tab.testClosure = {            [unowned self] // 去除循環參考            (str1: String) -> Void in            self.testLabel.text = "First Record:\(str1)"        }        self.view.addSubview(tab)    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }}

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Swift:閉包(Closures)

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.