標籤:閉包 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)