Swift學習筆記第五篇(閉包和列舉)

Deft_MKJing宓珂璟發表於2017-06-21

閉包

閉包:自包含的程式碼塊,可以在程式碼中被傳遞和使用,閉包可以捕獲和儲存其所在上下文任意常量和變數的引用 這就是所謂的閉包幷包裹著這些常量和變數,俗稱閉包

閉包三種形式
1.全域性函式是一個有名字但不會捕獲任何值得閉包
2.巢狀函式是一個有名字並且可以捕獲其封閉函式內值得閉包
3.閉包表示式是一個利用輕量級語法所寫的可以捕獲其上下文的變數和常量的匿名閉包

sorted函式為例

public func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element]

func someFunction(externalParameterName localParameterName: Int) {
function body goes here, and can use localParameterName
to refer to the argument value for that parameter
}
// 如果你希望函式的使用者在呼叫函式時提供引數名字,那就需要給每個引數除了區域性引數名外再定義一個外部引數名。外部引數名寫在區域性引數名之前,用空格分隔。

1.可以看到,by其實就是暴露給外部的引數名,而areInIncreasingOrder是內部變數名
2.引數:閉包函式 該閉包函式需要傳入與陣列型別相同的兩個值,並返回一個布林型別值來告訴sorted函式當排序結束後傳入的第一個引數排在第二個引數前面還是後面。如果第一個引數值出現在第二個引數值前面,排序閉包函式需要返回true,反之返回false
3.看下最普通的呼叫

let names = ["aaaa","ccc","fff","ggggg","tttt","hhhh"]

func sortClosureFunc(s1:String,s2:String)->Bool{
    return s1 > s2
}

var results = names.sorted(by: sortClosureFunc)

閉包語法和使用

{
    (parameters) -> returenType in
        statement
}
上面的程式碼可以簡化為如下
results = names.sorted(by: { (s1:String, s2:String) -> Bool in
    return s1 > s2
})
閉包的函式體部分由關鍵字in引入。 該關鍵字表示閉包的引數和返回值型別定義已經完成,閉包函式體即將開始。因此一行搞定
results = names.sorted(by: { (s1:String, s2:String) -> Bool in return s1 > s2})

閉包語法之根據上下文推斷

// 實際上任何情況下,通過內聯閉包表示式構造的閉包作為引數傳遞給函式時,都可以推斷出閉包的引數和返回值型別,這意味著您幾乎不需要利用完整格式構造任何內聯閉包
results = names.sorted(by: { s1,s2 in
    return s1 > s2
})

閉包語法之隱藏return

// 單表示式可以通過隱藏return關鍵字來隱藏單行返回的結果
results = names.sorted(by: {s1,s2 in s1 > s2})
print(results)

引數名稱縮寫

Swift 自動為行內函數提供了引數名稱縮寫功能,您可以直接通過$0,$1,$2來順序呼叫閉包的引數。
results = names.sorted(by: {$0 < $1})
print(results)

運算子函式

實際上還有一種更簡短的方式來撰寫上面例子中的閉包表示式。 Swift 的String型別定義了關於大於號 (>) 的字串實現,其作為一個函式接受兩個String型別的引數並返回Bool型別的值
results = names.sorted(by: >)
print(results)

尾隨閉包(系統預設)

如果您需要將一個很長的閉包表示式作為最後一個引數傳遞給函式,可以使用尾隨閉包來增強函式的可讀性
如果函式只需要閉包表示式一個引數,當您使用尾隨閉包時,您甚至可以把()省略掉

// 尾隨閉包 Closure Training
func someFunctionClosure(closure:()->()){
    // 函式體
}

// 老式寫法
someFunctionClosure(closure: {})

// 尾隨寫法
someFunctionClosure(){}


// 如果函式只需要閉包表示式一個引數,當您使用尾隨閉包時,您甚至可以把()省略掉。  推薦寫法
someFunctionClosure {

}
因此上面的Demo可以簡化成
results = names.sorted{$0<$1}
print(results)

列舉

普通寫法

為了理解下面的Demo 簡單介紹下列舉
語法
enum SomeEnumeration {
  // enumeration definition goes here
}
定義一個列舉
enum CompassPoint {
  case North
  case South
  case East
  case West
}

訪問賦值
var directionToHead = CompassPoint.West

已經確認directionToHead型別之後也可以這樣範文
directionToHead = .East

特有的寫法

// 商品條形碼 列舉
enum Barcode {
  case UPCA(Int, Int, Int)
  case QRCode(String)
}
// “定義一個名為Barcode的列舉型別,它可以是UPCA的一個相關值(Int,Int,Int),或者QRCode的一個字串型別(String)相關值。”

// 建立和賦值
var productBarcode = Barcode.UPCA(8, 85909_51226, 3)
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")

// 條件篩選列印
// 你可以在switch的 case 分支程式碼中提取每個相關值作為一個常量(用let字首)或者作為一個變數(用var字首)來使用
switch productBarcode {
case .UPCA(let numberSystem, let identifier, let check):
    println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
case .QRCode(let productCode):
    println("QR code with value of \(productCode).")
}
// 輸出 "QR code with value of ABCDEFGHIJKLMNOP.”

// 如果一個列舉成員的所有相關值被提取為常量,或者它們全部被提取為變數,為了簡潔,你可以只放置一個var或者let標註在成員名稱前
switch productBarcode {
case let .UPCA(numberSystem, identifier, check):
    println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
case let .QRCode(productCode):
    println("QR code with value of \(productCode).")
}
// 輸出 "QR code with value of ABCDEFGHIJKLMNOP."

列舉預設值和可選繫結判斷

// 定義
enum Planet: Int {
    case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
// 根據預設值推斷
let earthsOrder = Planet.Earth.rawValue
// earthsOrder is 3

// 可選繫結 ? or !
let positionToFind = 9
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .Earth:
        println("Mostly harmless")
    default:
        println("Not a safe place for humans")
    }
} else {
    println("There isn't a planet at position \(positionToFind)")
}
// 輸出 "There isn't a planet at position 9

// 第一個if根據後面的表示式傳入的是9,無法找到,因此返回的是nil,無法賦值給let常量,因此進入else分支,這也是可選值判斷的一個常用方法

閉包示例—官方文件的Demo

// 官方文件的閉包例子
enum HTTPResponse {
    case ok
    case error(Int)
}

let responses: [HTTPResponse] = [.error(500), .ok, .ok, .error(404), .error(403)]
let sortedResponses = responses.sorted {
    switch ($0, $1) {
    // Order errors by code
    case let (.error(aCode), .error(bCode)):
        return aCode < bCode

    // All successes are equivalent, so none is before any other
    case (.ok, .ok): return false

    // Order errors before successes
    case (.error, .ok): return true
    case (.ok, .error): return false
    }
}
print(sortedResponses)
//      Prints "[.error(403), .error(404), .error(500), .ok, .ok]"
[__lldb_expr_417.HTTPResponse.error(403), __lldb_expr_417.HTTPResponse.error(404), __lldb_expr_417.HTTPResponse.error(500), __lldb_expr_417.HTTPResponse.ok, __lldb_expr_417.HTTPResponse.ok]

閉包示例—map函式

// map numbers.map(<#T##transform: (Int) throws -> T##(Int) throws -> T#>)
// 使用 map 來遍歷集合並對集合中每一個元素進行同樣的操作
// 基本上就是預設尾隨閉包,引數只有閉包,省略(),省略return 單行表示式閉包可以通過隱藏return關鍵字來隱式返回單行表示式的結果

let digistName = [0:"zore",1:"one",2:"two",3:"three",4:"four",5:"five",6:"six",7:"seven",8:"eight",9:"nine"]
let numbers = [213,44,658]

let stringNumbers = numbers.map { (num:Int) -> String in
    var num1 = num
    var string = ""
    while num1 > 0{
        string = digistName[num1 % 10]! + string
        num1 = num1 / 10
    }
    return string
}
print(stringNumbers)
// ["twoonethree", "fourfour", "sixfiveeight"]

let stringNumber2 = numbers.map {NumberFormatter.localizedString(from: NSNumber.init(value: $0), number: .spellOut) }
print(stringNumber2)
// ["two hundred thirteen", "forty-four", "six hundred fifty-eight"]

捕獲上下文變數

閉包可以在其定義的上下文中捕獲常量或變數。 即使定義這些常量和變數的原域已經不存在,閉包仍然可以在閉包函式體內引用和修改這些值。

Swift最簡單的閉包形式是巢狀函式,也就是定義在其他函式的函式體內的函式。 巢狀函式可以捕獲其外部函式所有的引數以及定義的常量和變數

func makeIncreaseFunc(extensionNumber localNumber: Int) -> ()->Int{
    var totalAmount = 0;
    func increaseMent()->Int{
        totalAmount += localNumber
        return totalAmount
    }
    return increaseMent
}

let inc = makeIncreaseFunc(extensionNumber: 20)
inc() // 20
inc() // 40

let inc2 = makeIncreaseFunc(extensionNumber: 50)
inc2() // 50
inc2() // 100
makeIncreaseFunc 函式裡面巢狀了 increaseMent,巢狀函式increaseMent從上下文捕獲了兩個值 totalAmount 和 localNumber 之後 makeIncreaseFunc把 increaseMent作為返回值返回,每次外部呼叫 返回值的時候,就會返回totalAmount的值

我們將函式賦給變數,這樣我們可以通過變數來呼叫函式。執行結果使得我們可以發現,每次呼叫makeIncreaseFunc其實是建立了一個新的物件,inc1,和inc2並不一樣。它們有著自己的值,之間並不共享。這說明,這些函式是一等函式,它們是物件,可以有多個例項,可以被賦給變數,感覺像是一個解構函式,內部會建立對應的物件,然後物件會引用外部函式的一些值,從而達到區域性變數常駐記憶體的情況

/*
func increaseMent()->Int{
    totalAmount += localNumber
    return totalAmount
}
*/
// 單獨看這個函式,沒有傳任何引數,而是通過值捕獲和increaseMent一樣儲存在的記憶體中  捕獲是強引用,保證makeIncreaseFunc掛了的時候,其引數和內部引數都能繼續使用

對比下簡單的兩個例子

1.函式巢狀,沒有返回函式

// 如果單純的函式巢狀,區域性變數使用完之後就會被回收,再次呼叫函式的時候就會是原先的資料
func add(num:Int)->Int
{
    var value = 100
    func addDouble()->Int{
        value += num
        return value
    }
    return addDouble()
}
let testFun = add
testFun(200) // 300
testFun(200) // 300
testFun(200) // 300

2.函式巢狀,返回內部函式,實現閉包

// 內部巢狀函式通過返回值返回,外部進行引用,讓區域性變數常駐記憶體,這才算是個閉包
func add2(num:Int)->()->Int
{
    var value = 100

    func addDouble()->Int{
        value += num
        return value
    }
    return addDouble
}

let testFunc2 = add2(num: 200)
testFunc2() // 300
testFunc2() // 500
testFunc2() // 700

個人總結

1.在說閉包之前,需要先清楚“自由變數”的概念。在某個作用域中,如果使用未在本作用域中宣告的變數,對於此作用域來說,該變數就是一個自由變數。
2.閉包,是引用了自由變數的函式。這個被引用的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。另一種說法認為閉包並不是函式,而是由函式和與其相關的引用環境組合而成的實體。這是因為,閉包在執行時可以有多個例項,不同的引用環境和相同的函式組合可以產生不同的例項。而函式只會有一個例項。我感覺如果把閉包和block一樣當做一個物件,這就很好理解了,你的值被物件引用了,那麼這個物件未銷燬之前,無論之前創造這些變數的物件是否有銷燬,只有有強指標,這些變數都還能被使用
3.閉包的缺點就是常駐記憶體,會增大記憶體使用量,使用不當很容易造成記憶體洩露。
4.特性:函式巢狀函式,內部函式可以引用其外部變數和引數,能讓區域性變數保活
5.好處:可以是區域性變數常駐記憶體,自由自支配釋放,避免全域性變數的汙染,私有成員的存在

相關文章