1. 程式人生 > >Swift:閉包(Closures)

Swift:閉包(Closures)

ins 總結 ole n) 而在 unsafe width content decode

一、 基本概念

閉包(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 = self
lazy 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)