Swift:閉包(Closures)

來源:互聯網
上載者:User

標籤:操作   ogr   迴圈   imp   函數嵌套   地方   ash   code   ini   

一、 基本概念 

閉包(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方法。


另外一種方式:使用閉包方式 
完整閉包寫法是在花括弧內有參數列表和傳回值。用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 = selflazy var asHtml: () -> String = {        if let text = weakSelf.text {            return "<\(weakSelf.name)>\(weakSelf.text)</\(weakSelf.name)>"        } else {            return "<\(weakSelf.name)>"        }    }

注意:當中另外一種和第三種方法使用比較常見。


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


功能非常easy
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)

相關文章

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.