Swift 烧脑体操(二) - 函数的参数
前言
\\Swift 其實比 Objective-C 復雜很多,相對于出生于上世紀 80 年代的 Objective-C 來說,Swift 融入了大量新特性。這也使得我們學習掌握這門語言變得相對來說更加困難。不過一切都是值得的,Swift 相比 Objective-C,寫出來的程序更安全、更簡潔,最終能夠提高我們的工作效率和質量。
\\Swift 相關的學習資料已經很多,我想從另外一個角度來介紹它的一些特性,我把這個角度叫做「燒腦體操」。什么意思呢?就是我們專門挑一些比較費腦子的語言細節來學習。通過「燒腦」地思考,來達到對 Swift 語言的更加深入的理解。
\\這是本體操的第二節,練習前請做好準備運動,保持頭腦清醒。
\\準備運動:基礎知識
\\\\面向對象語言的世界觀
\\對于很多面向對象的編程語言來說,在思考問題時,總是把「對象」作為考慮問題的基本出發點。
\\面向對象的程序設計通過以下三大規則,構建出程序設計的基礎,它們是:
\\在以上三大準則的基礎上,再引入一些設計原則,比如:
\\于是,程序世界就基于這些規則和原則,產生出了設計模式,進而能更加精準地指導我們的編程行為。這就像我們學習幾何,先學習幾條公理,然后以后的大量定理都通過公理證明而來。
\\舉個例子,單例模式(Singleton Pattern)其實就是封裝和單一職責原則的產物。代理模式(Delegate Pattern) 也是單一職責和封裝中的面向接口設計的思想的產物。
\\但是,在面向對象語言的世界觀里面,函數都是作為一個附屬物存在的。函數通常附屬于一個具體類的某個方法中。或許有一個函數它根本都不需要任何對象作為容器,為了這個世界的統一,我們還是會構造一個類,把這個函數放進去。比如,在小猿搜題中,我們就有一個叫 ImageUtils 的類,里面放了操作圖像的各種各樣的靜態方法,有一些圖象操作函數其實也不太通用,但是總得找一個類放不是。
\\在一些面向對象語言的世界中,如果把對象稱作 OOP 的一等公民的話,那么函數就是二等公民。
\\函數式編程
\\在 Swift 的世界中,函數并不是二等公民。是的,Swift 引入了大量函數式編程的特性,使得我們能夠把函數當作一等公民來對待。
\\一等公民有什么權利呢?那就是函數可以像對象一樣,被賦值、被當作參數傳遞、參與計算或者當作結果被返回。
\\我們先來看一段函數被賦值的例子,在下例中,我們將一個函數賦值給一個名為 myFunc 的變量,然后調用它。
\\\let myFunc = { \ () -\u0026gt; String in\ return \"Tang Qiao\"\}\\let value = myFunc()\// value 的值為 \"Tang Qiao\"\\\我們再來看一個函數被當作運算結果返回的例子。在這個例子中,我們希望構造一個「加法器」工廠,這個工廠能夠接受一個參數 addValue,返回一個加法器函數,這個加法器函數能夠將傳遞的參數加 addValue 之后返回。以下是實現的代碼:
\\\func addFactory(addValue: Int) -\u0026gt; (Int -\u0026gt; Int) {\ func adder(value: Int) -\u0026gt; Int {\ return addValue + value\ }\ return adder\}\\\有了上面這個函數,我們就可以構造一個 +2 的函數,然后使用它,如下所示:
\\\let add2 = addFactory(2) // 構造一個 +2 的函數\let result = add2(3) // 運算,傳入 3,得到 5\\\函數的參數
\\但是在本次「燒腦體操」中,全面介紹函數式編程明顯不太現實,所以我們僅從函數的參數來深入學習一下,看看在 Swift 語言中,函數的參數能夠有多復雜。
\\參數的省略
\\我們先來簡單看看函數參數的省略吧,因為有類型推導,函數的參數在 Swift 中常??梢员皇÷缘?#xff0c;特別以匿名函數(閉包)的形式存在的時候。
\\我們來看一個數組排序的例子:
\\\let array = [1, 3, 2, 4]\let res = array.sort {\ (a: Int, b: Int) -\u0026gt; Bool in\ return a \u0026lt; b\}\\\\如果一個函數返回類型可以通過推導出來,則返回類型可以省略。所以以上代碼中的 -\u0026gt; Bool 可以刪掉,變成:
\\\let array = [1, 3, 2, 4]\let res = array.sort {\ (a: Int, b: Int) in\ return a \u0026lt; b\}\\\如果一個函數的參數類型可以推導出來,則參數的類型可以省略。所以以上代碼中的 : Int 可以刪掉,變成:
\\\let array = [1, 3, 2, 4]\let res = array.sort {\ (a, b) in\ return a \u0026lt; b\}\\\\如果函數參數的個數可以推導出來,也可以不寫參數。那怎么使用這些參數呢?可以用 $0, $1 這樣的方式來引用參數。所以以上代碼中的 (a, b) 可以刪掉,因為這樣的話,參數和返回值都省略了,所以in也可以省略了,變成:
\\\let array = [1, 3, 2, 4]\let res = array.sort {\ return $0 \u0026lt; $1\}\\\Swift 還有一個規則,如果函數的 body 只有一行,則可以把 return 關鍵字省略了,所以以上代碼可以進一步簡化成:
\\\let array = [1, 3, 2, 4]\let res = array.sort {\ $0 \u0026lt; $1\}\\\最后一個簡化規則更加暴力,因為 \u0026lt; 符號也是一個函數,它接受的參數個數,類型和返回值與 sort 函數需要的一樣,所以可以直接簡化成:
\\\let array = [1, 3, 2, 4]\let res = array.sort( \u0026lt; )\\\拿這個的方法,同樣可以把我們剛剛寫的 addFactory 做簡化,最后簡化成如下的形式:
\\\// 簡化前\func addFactory(addValue: Int) -\u0026gt; (Int -\u0026gt; Int) {\ func adder(value: Int) -\u0026gt; Int {\ return addValue + value\ }\ return adder\}\// 簡化后\func addFactory(addValue: Int) -\u0026gt; (Int -\u0026gt; Int) {\ return { addValue + $0 }\}\\\函數參數中的其它關鍵字
\\有些時候,我們的函數接受的參數就是另外一個函數,例如 sort,map,所以我們在看代碼的時候,需要具備熟悉這種寫法的能力。
\\我們來看看數組的 map 函數的定義吧:
\\\public func map\u0026lt;T\u0026gt;(@noescape transform: (Self.Generator.Element) throws -\u0026gt; T) rethrows -\u0026gt; [T]\\\這個函數定義中出現了幾個我們剛剛沒提到的關鍵詞,我們先學習一下。
\\@noescape
\\@noescape,這是一個從 Swift 1.2 引入的關鍵字,它是專門用于修飾函數閉包這種參數類型的,當出現這個參數時,它表示該閉包不會跳出這個函數調用的生命期:即函數調用完之后,這個閉包的生命期也結束了。以下是蘋果的文檔原文:
\\\A new @noescape attribute may be used on closure parameters to functions. This indicates that the parameter is only ever called (or passed as an @noescape parameter in a call), which means that it cannot outlive the lifetime of the call. This enables some minor performance optimizations, but more importantly disables the self. requirement in closure arguments.
\\\什么情況下一個閉包參數會跳出函數的生命期呢?很簡單,我們在函數實現內,將一個閉包用 dispatch_async 嵌套,這樣這個閉包就會在另外一個線程中存在,從而跳出了當前函數的生命期。這樣做主要是可以幫助編譯器做性能的優化。
\\如果你對此感興趣,這里有一些更詳細的介紹供你學習:
\\- https://stackoverflow.com/questions/28427436/noescape-attribute-in-swift-1-2/28428521#28428521\\t
- http://nshint.io/blog/2015/10/23/noescape-attribute/\
throws 和 rethrows
\\throws 關鍵字表示:這個函數(閉包)可能拋出異常。而 rethrows 關鍵字表示:這個函數如果拋出異常,僅可能是因為傳遞給它的閉包的調用導致了異常。
\\throws 關鍵字的存在大家都應該能理解,因為總有一些異常可能在設計的時候希望暴露給上層,throws 關鍵字的存在使得這種設計成為可能。
\\那么為什么會有 rethrows 關鍵字呢?在我看來,這是為了簡化很多代碼的書寫。因為一旦一個函數會拋出異常,按 Swift 類型安全的寫法,我們就需要使用 try 語法。但是如果很多地方都需要寫 try 的話,會造成代碼非常啰嗦。 rethrows 關鍵字使得一些情況下,如果你傳進去的閉包不會拋出異常,那么你的調用代碼就不需要寫 try。
\\如果你對此感興趣,這里有一些更詳細的介紹供你學習:
\\- http://robnapier.net/re-throws\
函數作為函數的參數
\\剛剛說到,函數作為一等公民,意味著函數可以像對象一樣,被當作參數傳遞或者被當作值返回。對此,我們專門有一個名稱來稱呼它,叫做高階函數(higher-order function)。
\\在剛剛那個數組的 map 函數中,我們就看到了它接著另外一個函數作為參數,這個函數接受數組元素類型作為參數,返回一個新類型。
\\\public func map\u0026lt;T\u0026gt;(@noescape transform: (Self.Generator.Element) throws -\u0026gt; T) rethrows -\u0026gt; [T]\\\有了 map 函數,我們就可以輕松做數組元素的變換了。如下所示:
\\\let arr = [1, 2, 4]\// arr = [1, 2, 4]\\let brr = arr.map {\ \"No.\" + String($0)\}\// brr = [\"No.1\ 與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Swift 烧脑体操(二) - 函数的参数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis编译安装:make 的新错误-
- 下一篇: vagrant --- vagrant部