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

來源:互聯網
上載者:User
閉包

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

閉包三種形式
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 Trainingfunc 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 = 9if 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() // 20inc() // 40let inc2 = makeIncreaseFunc(extensionNumber: 50)inc2() // 50inc2() // 100makeIncreaseFunc 函數裡面嵌套了 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 = addtestFun(200) // 300testFun(200) // 300testFun(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() // 300testFunc2() // 500testFunc2() // 700
個人總結

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

相關文章

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.