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)
以下用Swift標準庫中的sort方法來一步步簡化閉包寫法
sort函數須要兩個參數
參數一:數組
參數二:一個閉包:帶有兩個參數,這兩個參數類型與數組中的元素類型同樣,返回值是Bool
數組:
var names = ["Swift", "Arial", "Soga", "Donary"]
第一種方式:使用函數
func backwards(firstString: String, secondString: String) -> Bool { return firstString > secondString // 降序排序 }
sort(&names, backwards)
這樣的方式的使用相當於回調backward方法。
完整閉包寫法是在花括號內有參數列表和返回值。用keywordin表明閉包體的開始
(1) (firstString: String, secondString: String) 閉包參數列表
(2) -> Bool 指明閉包返回值類型是Bool
(3) inkeyword表明閉包體的開始
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;
方式一: 使用 unowned keyword
在Swift中的閉包中也存在相同的問題。事實上大致思路是一致的,我們能夠使用unowned keyword。
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 “弱化”。從而解除了循環引用。
註: unowned keyword就相當於oc中的__unsafe_unretained,當不安全指針指向的對象銷毀時。指針依舊指向曾經指向的內存地址(野指針)
方式二: 使用 weak keyword
lazy var asHtml: () -> String = { [weak self] in if let text = self.text { return "<\(self.name)>\(self.text)</\(self.name)>" } else { return "<\(self.name)>" } }
註: weakkeyword相當於oc中的__weak, 當弱指針指向的對象銷毀時。指針自己主動指向nil
方式三: oc中相應的weakkeyword
weak var weakSelf = self lazy var asHtml: () -> String = { if let text = weakSelf.text { return "<\(weakSelf.name)>\(weakSelf.text)</\(weakSelf.name)>" } else { return "<\(weakSelf.name)>" } }
註意:當中另外一種和第三種方法使用比較常見。
UI界面例如以下:
1. 上面灰色的View就是自己定義的View(TestView.swift)。它包括一個文本輸入框和一個“GO”button。
2. 後面這一塊就是控制器的View(ViewController.swift),它裏面包括一個testLabel。
3. 點擊"GO"button後將文本框中的內容回調到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”button則會調用其方法。註意到閉包的定義
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)