【Swift學習】Swift編程之旅---閉包(十一)

来源:http://www.cnblogs.com/salam/archive/2016/04/25/5427743.html
-Advertisement-
Play Games

閉包是可以在代碼中被傳遞和使用的自包含功能模塊,它很像c和oc中的block,和.net中的lambdasbas表達式。 閉包可以捕獲和存儲其所在上下文中任意常量和變數的引用。 包裹著這些常量和變數的包俗稱閉包。Swift會為您管理在捕獲過程中涉及到的記憶體操作。下麵是閉包的3中表現形式 全局函數是一 ...


  閉包是可以在代碼中被傳遞和使用的自包含功能模塊,它很像c和oc中的block,和.net中的lambdasbas表達式。

  閉包可以捕獲和存儲其所在上下文中任意常量和變數的引用。 包裹著這些常量和變數的包俗稱閉包。Swift會為您管理在捕獲過程中涉及到的記憶體操作。下麵是閉包的3中表現形式

  • 全局函數是一個有名字但不會捕獲任何值的閉包
  • 嵌套函數是一個有名字並可以捕獲其封閉函數域內值的閉包
  • 閉包表達式是一個利用輕量級語法所寫的可以捕獲其上下文中變數或常量值的沒有名字的閉包

  Swift的閉包表達式擁有簡潔的風格,並鼓勵在常見場景中以實現語法優化,主要優化如下: 

  •  利用上下文推斷參數和返回值類型
  •  單表達式(single-expression)閉包可以省略 return 關鍵字
  •  參數名稱簡寫
  •  Trailing 閉包語法
    閉包表達式    嵌套函數是一種在較複雜函數中方便進行命名和定義自包含代碼模塊的方式。 當然,有時候撰寫小巧的沒有完整定義和命名的類函數結構也是很有用處的,尤其是在處理一些函數並需要將另外一些函數作為該函數的參數時。下麵閉包表達式的例子通過使用幾次迭代展示了 sort 函數定義和語法優化的方式     sort方法    Swift 標準庫提供了 sort 函數,會根據您提供的排序閉包將已知類型數組中的值進行排序。 一旦排序完成,函數會返回一個與原數組大小相同的新數組,該數組中包含已經正確排序的同類型元素。 下麵的閉包表達式示例使用 sort 函數對一個String類型的數組進行字母逆序排序,以下是初始數組值:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

 

排序函數有兩個參數: 1. 已知類型值的數組。 2. 一個閉包,採用相同類型的數組的內容的兩個參數,並返回一個布爾值來表示是否將第一個值在排序時放到第二個值的前面或是後面。如果第一個值應該出現第二個值之前,閉包需要返回true,否則返回false。   該例子對一個 String 類型的數組進行降序排序,因此排序閉包需為 (String, String) -> Bool 類型的函數。
func backwards(s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversed = names.sort(backwards)
// reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

 

如果第一個字元串 (s1) 大於第二個字元串 (s2),backwards 函數則返回 true,表示在新的數組中 s1 應該出現在 s2 前。 字元中的 "大於" 表示 "按照字母順序後出現"。 這意味著字母 "B" 大於字母 "A", 字元串 "Tom" 大於字元串 "Tim"。 其將進行字母逆序排序,"Barry" 將會排在 "Alex" 之前,依次類推。   然而,這是一個相當冗長的方式,本質上只是寫了一個單表達式函數 (a > b)。 在下麵的例子中,利用閉合表達式語法可以更好的構造一個內聯排序閉包。       閉包表達式語法 閉包表達式語法可以使用常量、變數和 inout 類型作為參數,但不提供預設值。 也可以在參數列表的最後使用可變參數。元組也可以作為參數和返回值。
reversed = sort(names, { (s1: String, s2: String) -> Bool in 
    return s1 > s2 
}) 

 

需要註意的是內聯閉包參數和返回值類型聲明與 backwards 函數類型聲明相同。 在這兩種方式中,都寫成了 (s1: String, s2: String) -> Bool類型。 然而在內聯閉包表達式中,函數和返回值類型都寫在大括弧內,而不是大括弧外。   閉包的函數體部分由關鍵字 in 引入。 該關鍵字表示閉包的參數和返回值類型定義已經完成,閉包函數體即將開始。   因為這個閉包的函數體部分如此短以至於可以將其改寫成一行代碼
reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )

 這說明 sort 函數的整體調用保持不變,一對圓括弧仍然包裹住了函數中整個參數集合。而其中一個參數現在變成了內聯閉包 。

    根據上下文推斷類型   因為排序閉包是作為函數的參數進行傳入的,Swift可以推斷其參數和返回值的類型。 sort 期望第二個參數是類型為 (String, String) -> Bool 的函數,因此實際上 String, String 和 Bool 類型並不需要作為閉包表達式定義中的一部分。 因為所有的類型都可以被正確推斷,返回箭頭 (->) 和 圍繞在參數周圍的括弧也可以被省略:
reversed = sort(names, { s1, s2 in return s1 > s2 } ) 

 

際上任何情況下,通過內聯閉包表達式構造的閉包作為參數傳遞給函數時,都可以推斷出閉包的參數和返回值類型,這意味著您幾乎不需要利用完整格式構造任何內聯閉包。   然而,你也可以使用明確的類型,如果你想它避免讀者閱讀可能存在的歧義,這樣還是值得鼓勵的。這個排序函數例子,閉包的目的是很明確的,即排序被替換,而且對讀者來說可以安全的假設閉包可能會使用字元串值,因為它正協助一個字元串數組進行排序。     單行表達式閉包可以省略 return 單行表達式閉包可以通過隱藏 return 關鍵字來隱式返回單行表達式的結果,如上版本的例子可以改寫為:
reversed = sort(names, { s1, s2 in s1 > s2 } ) 

 

在這個例子中,sort 函數的第二個參數函數類型明確了閉包必須返回一個 Bool 類型值。 因為閉包函數體只包含了一個單一表達式 (s1 > s2),該表達式返回 Bool 類型值,因此這裡沒有歧義,return關鍵字可以省略。     參數名簡寫 Swift 自動為內聯函數提供了參數名稱簡寫功能,您可以直接通過 $0,$1,$2等名字來引用的閉包的參數的值。   如果您在閉包表達式中使用參數名稱簡寫,您可以在閉包參數列表中省略對其的定義,並且對應參數名稱簡寫的類型會通過函數類型進行推斷。 in 關鍵字也同樣可以被省略,因為此時閉包表達式完全由閉包函數體構成:    
reversed = sort(names, { $0 > $1 } ) 

 

  運算符函數

實際上還有一種更簡短的方式來撰寫上面例子中的閉包表達式。 Swift的 String 類型定義了關於大於號 (>) 的字元串實現,讓其作為一個函數接受兩個 String 類型的參數並返回 Bool 類型的值。 而這正好與 sort 函數的第二個參數需要的函數類型相符合。 因此,您可以簡單地傳遞一個大於號,Swift可以自動推斷出您想使用大於號的字元串函數實現:
reversed = sort(names, >) 

 

   Trailing 閉包 如果您需要將一個很長的閉包表達式作為最後一個參數傳遞給函數,可以使用 trailing 閉包來增強函數的可讀性。   Trailing 閉包是一個書寫在函數括弧之外(之後)的閉包表達式,函數支持將其作為最後一個參數調用。  
func someFunctionThatTakesAClosure(closure: () -> ()) { 
    // 函數體部分 
} 
  
// 以下是不使用 trailing 閉包進行函數調用 
  
someFunctionThatTakesAClosure({ 
    // 閉包主體部分 
}) 
  
// 以下是使用 trailing 閉包進行函數調用 
  
someFunctionThatTakesAClosure() { 
    // 閉包主體部分 
} 

 註意:如果函數只需要閉包表達式一個參數,當您使用 trailing 閉包時,您甚至可以把 () 省略掉。

在上例中作為 sort 函數參數的字元串排序閉包可以改寫為:

reversed = sort(names) { $0 > $1 } 
當閉包非常長以至於不能在一行中進行書寫時,Trailing 閉包就變得非常有用。 舉例來說,Swift 的 Array 類型有一個 map 方法,其獲取一個閉包表達式作為其唯一參數。 數組中的每一個元素調用一次該閉包函數,並返回該元素所映射的值(也可以是不同類型的值)。 具體的映射方式和返回值類型由閉包來指定。   當提供給數組閉包函數後,map 方法將返回一個新的數組,數組中包含了與原數組一一對應的映射後的值。   下例介紹瞭如何在 map 方法中使用 trailing 閉包將 Int 類型數組 [16,58,510] 轉換為包含對應 String 類型的數組 ["OneSix", "FiveEight", "FiveOneZero"]:
let digitNames = [ 
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four", 
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine" 
] 
let numbers = [16, 58, 510] 

 

上面的代碼創建了整數數字到他們的英文名字之間映射字典。 同時定義了一個準備轉換為字元串的整型數組。   您現在可以通過傳遞一個 trailing 閉包給 numbers 的 map 方法來創建對應的字元串版本數組。 需要註意的時調用 numbers.map 不需要在 map 後麵包含任何括弧,因為只需要傳遞閉包表達式這一個參數,並且該閉包表達式參數通過 trailing 方式進行撰寫:  
let strings = numbers.map { 
    (var number) -> String in 
    var output = "" 
    while number > 0 { 
        output = digitNames[number % 10]! + output 
        number /= 10 
    } 
    return output 
} 
// strings 常量被推斷為字元串類型數組,即 String[] 
// 其值為 ["OneSix", "FiveEight", "FiveOneZero"] 

 

map 在數組中為每一個元素調用了閉包表達式。 您不需要指定閉包的輸入參數 number 的類型,因為可以通過要映射的數組類型進行推斷。   閉包 number 參數被聲明為一個變數參數 ,因此可以在閉包函數體內對其進行修改。 閉包表達式制定了返回值類型為 String,以表明存儲映射值的新數組類型為String。     閉包表達式在每次被調用的時候創建了一個字元串並返回。 其使用求餘運算符 (number % 10) 計算最後一位數字並利用 digitNames 字典獲取所映射的字元串。 註意:  字典 digitNames 下標後跟著一個嘆號 (!),因為字典下標返回一個可選值 (optional value),表明即使該 key不存在也不會查找失敗。 在上例中,它保證了 number % 10 可以總是作為一個 digitNames 字典的有效下標 key。 因此嘆號可以用於強展開 (force-unwrap) 存儲在可選下標項中的 String 類型值。     從 digitNames 字典中獲取的字元串被添加到輸出的前部,逆序建立了一個字元串版本的數字。 (在表達式 number % 10中,如果number為16,則返回6,58返回8,510返回0)。   number 變數之後除以10。 因為其是整數,在計算過程中未除盡部分被忽略。 因此 16變成了1,58變成了5,510變成了51。   整個過程重覆進行,直到 number /= 10 為0,這時閉包會將字元串輸出,而map函數則會將字元串添加到所映射的數組中。   上例中 trailing 閉包語法在函數後整潔封裝了具體的閉包功能,而不再需要將整個閉包包裹在 map 函數的括弧內。     捕獲 (Caputure)
閉包可以在其定義的上下文中捕獲常量或變數。 即使定義這些常量和變數的原作用域已經不存在,閉包仍然可以在閉包函數體內引用和修改這些值。   Swift最簡單的閉包形式是嵌套函數,也就是定義在其他函數體內的函數。 嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變數。   下例為一個叫做 makeIncrementor 的函數,其包含了一個叫做 incrementor 嵌套函數。 嵌套函數 incrementor 從上下文中捕獲了兩個值,runningTotal 和 amount。 之後 makeIncrementor 將 incrementor 作為閉包返回。 每次調用 incrementor 時,其會以 amount 作為增量增加 runningTotal 的值。
func makeIncrementor(forIncrement amount: Int) -> () -> Int { 
    var runningTotal = 0 
    func incrementor() -> Int { 
        runningTotal += amount 
        return runningTotal 
    } 
    return incrementor 
} 

 

makeIncrementor 返回類型為 () -> Int。 這意味著其返回的是一個函數,而不是一個簡單類型值。 該函數在每次調用時不接受參數只返回一個 Int 類型的值。 關於函數返回其他函數的內容,請查看Function Types as Return Types。   makeIncrementor 函數定義了一個整型變數 runningTotal (初始為0) 用來存儲當前增加總數。 該值通過 incrementor 返回。   makeIncrementor 有一個 Int 類型的參數,其外部命名為 forIncrement, 內部命名為 amount,表示每次 incrementor 被調用時 runningTotal 將要增加的量。   incrementor 函數用來執行實際的增加操作。 該函數簡單地使 runningTotal 增加 amount,並將其返回。   如果我們單獨看這個函數,會發現看上去不同尋常:
func incrementor() -> Int { 
    runningTotal += amount 
    return runningTotal 
} 

 

incrementor 函數並沒有獲取任何參數,但是在函數體內訪問了 runningTotal 和 amount 變數。這是因為其通過捕獲在包含它的函數體內已經存在的 runningTotal 和 amount 變數而實現。   由於沒有修改 amount 變數,incrementor 實際上捕獲並存儲了該變數的一個副本,而該副本隨著 incrementor 一同被存儲。   然而,因為每次調用該函數的時候都會修改 runningTotal 的值,incrementor 捕獲了當前 runningTotal 變數的引用,而不是僅僅複製該變數的初始值。捕獲一個引用保證了當 makeIncrementor 結束時候並不會消失,也保證了當下一次執行 incrementor 函數時,runningTotal 可以繼續增加。 註意: Swift 會決定捕獲引用還是拷貝值。 您不需要標註 amount 或者 runningTotal 來聲明在嵌入的 incrementor 函數中的使用方式。 Swift 同時也處理 runingTotal 變數的記憶體管理操作,如果不再被 incrementor 函數使用,則會被清除。  
let incrementByTen = makeIncrementor(forIncrement: 10) 

 該例子定義了一個叫做 incrementByTen 的常量,該常量指向一個每次調用會加10的 incrementor 函數。 調用這個函數多次可以得到以下結果:

incrementByTen() 
// 返回的值為10 
incrementByTen() 
// 返回的值為20 
incrementByTen() 
// 返回的值為30 

 如果您創建了另一個 incrementor,其會有一個屬於自己的獨立的 runningTotal 變數的引用。 下麵的例子中,incrementBySevne 捕獲了一個新的 runningTotal 變數,該變數和 incrementByTen 中捕獲的變數沒有任何聯繫:

 
let incrementBySeven = makeIncrementor(forIncrement: 7) 
incrementBySeven() 
// 返回的值為7 
incrementByTen() 
// 返回的值為40 

 如果您閉包分配給一個類實例的屬性,並且該閉包通過指向該實例或其成員來捕獲了該實例,您將創建一個在閉包和實例間的強引用環。 Swift 使用捕獲列表來打破這種強引用環

      閉包是引用類型
上面的例子中,incrementBySeven 和 incrementByTen 是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變數值。 這是因為函數和閉包都是引用類型。   無論您將函數/閉包賦值給一個常量還是變數,您實際上都是將常量/變數的值設置為對應函數/閉包的引用。 上面的例子中,incrementByTen 指向閉包的引用是一個常量,而並非閉包內容本身。   這也意味著如果您將閉包賦值給了兩個不同的常量/變數,兩個值都會指向同一個閉包:
let alsoIncrementByTen = incrementByTen 
alsoIncrementByTen() 
// 返回的值為50 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 學習要點: 1.案例內容 2.關於內容 主講教師:李炎恢 本節課我們製作一下子案例欄目和關於欄目。 一.案例內容 //案例內容 1-4 個根據不同顯示比例展示 //CSS 部分 二.關於欄目 //左右兩欄即可 //CSS 部分 ...
  • 自動佈局 1 什麼是自動佈局? UI 根據不同尺寸的屏幕動態的佈局控制項大小. 2 作用 適配所有機型 3 佈局中常用的參數 autoResizing就是一個相對於父控制項的佈局解決方法;由於它只能相對父控制項佈局;因此不能約束子控制項之間關係. 所以出現了aotolayout,既可以設置父子控制項之間的關係 ...
  • 鍵路徑 在一個給定的實體中,同一個屬性的所有值具有相同的數據類型。 鍵-值編碼技術用於進行這樣的查找—它是一種間接訪問對象屬性的機制。 - 鍵路徑是一個由用點作分隔符的鍵組成的字元串,用於指定一個連接在一起的對象性質序列。第一個鍵的性質是由先前的性質決定的,接下來每個鍵的值也是相對於其前面的性質。 ...
  • 自定義 cell 1 什麼是自定義 cell 自定義 cell 即 tableView,collectionView,scrollView中的 cell 使用的時候不能滿足我們使用 cell 的需求,需要自己定義一個 cell. 2 cell 的重用 原因:cell 的顯示原理的,一個 cell 顯 ...
  • 當前蘋果已經禁止了,通過IOS應用直接跳轉APP下載鏈接的方法。但是仍然可以使用另外一種方法直接跳轉AppStore。 這種方法需要增加一個類庫StoreKit.framework。 這裡使用這功能是為用戶提供更新,下麵說下我實現這個功能的詳細步驟。 一、增加一個網頁到伺服器上去,title增加你當 ...
  • 一,效果圖。 二,工程圖。 三,代碼。 RootViewController.h RootViewController.m myCell.h myCell.m CardViewController.h CardViewController.m ...
  • 一、OC簡介 在C語言的基礎上,增加了一層最小的面向對象語法;完全相容C語言;可以在OC代碼中混入C語言代碼,甚至是C++代碼;可以使用OC開發Mac OS X平臺和iOS平臺的應用程式。 二、OC語法預覽 (一)關鍵字 基本上所有的關鍵字都是以@開頭的,如@interface @implement ...
  • 本文內容根據個人自學整理記錄,理解不當之處,希望大家批評指正,大家相互學習,寫學習歸納,寫博客是個好習慣,希望能夠堅持下去。 在前一篇文章當中介紹了 Android 的系統框架,主要是為了讓大家對Android 系統的內部層次結構有個清晰的認識,是開發 Android 程式開發的基礎。對 Andro ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...