最近買了本王巍寫的《Swifter 100個Swift2開發必備Tip》,之前看介紹以及看了此書序言后,確定此書應該是本不錯的涉及Swift開發技巧的學習材料,故決定以此書講述內容為題材,把自己對這些知識點的掌握以及由此引申出的知識及開發技巧歸納總結,一來便于自己鞏固,二來也希望能幫到更多的喜歡Swift的開發者。
柯里化(Currying)
相信很多朋友都跟我一樣,第一次聽到這個名詞,查了下關于該術語的解釋:
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數且返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,盡管它是 Moses Schnfinkel 和 Gottlob Frege 發明的。
舉個Swift中柯里化的實例,如下:
func addTwoNumbers(num: Int)(another: Int) -> Int {
return num + another
}
let addThree = addTwoNumbers(3)
let result = addThree(another: 6) //result is 9
從這個例子大家應該可以了解到柯里化在Swift中的用法,那么這個技巧在實際開發中有什么用呢?作者給我們舉了個例子,就是在Swift中Selector只能通過字符串來生成,這個在編譯期間是無法檢查的,那為了控制風險,可以采用Ole Begemann提出的利用柯里化來對Target-Action模式進行封裝,從而可以不用Selector方式來達到目的。
相信很多朋友和我一樣,對這里所述內容不是很清楚,很有可能是因為大家接觸Swift都相對比較晚,這時的Swift版本已經是2.2以后了。好,我就給大家講一下Swift語言在這個知識點上的變化。在Swift2.2之前,Selector需要傳入字符串,并且沒有自動補全,這樣開發者在輸入時很容易出錯。
let button = UIButton(type: .System)
button.addTarget(self, action: Selector(“buttonTapped:”), forControlEvents: .TouchUpInside)
...
func buttonTapped(sender: UIButton) { }
從Swift2.2開始,Selector寫法更加安全了,有了自動補全,可以在編譯階段進行檢查。寫法如下:
button.addTarget(self, action: #selector(ViewController.buttonTapped(_:)), forControlEvents: .TouchUpInside)
好了,了解了Swift語言Selector語法的變化,我們再回來看Ole Begemann利用柯里化來對Target-Action模式進行的封裝。首先給出了Swift中實例方法的本質
An instance method in Swift is just a type method that takes the instance as an argument and returns a function which will then be applied to the instance.
簡單翻譯就是:Swift中的實例方法就是一個類型方法,它以實例作為方法的第一個參數,并返回一個應用于該實例的函數。對這個概念也舉了個例子如下:
class BankAccount {
var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
}
普通青年用法:
let account = BankAccount()
account.deposit(100) // balance is now 100
文藝青年用法:
let depositor = BankAccount.deposit
depositor(account)(100) // balance is now 200
depositor常量可以看做是類似C語言中實例方法的函數指針,也可以看做
let depositor: BankAccount -> (Double) -> ()
也就是說,這個函數有一個參數,就是BankAccount的實例,返回值為另一個函數,該函數接受一個Double類型的參數,沒有返回值。
BankAccount.deposit(account)(100) // balance is now 300
我們如果連起來,再回頭看開頭介紹的實例方法的概念,就應該理解的更清楚了。
好明白了概念,我們再來看它是如何不依賴OC的消息分發機制(Selector方式),而用純Swift的方式實現Target-Action模式的。
protocol TargetAction {
func performAction()
}
struct TargetActionWrapper<T: AnyObject> : TargetAction {
weak var target: T?
let action: (T) -> () -> ()
func performAction() -> () {
if let t = target {
action(t)()
}
}
}
enum ControlEvent {
case TouchUpInside
case ValueChanged
// ...
}
class Control {
var actions = [ControlEvent: TargetAction]()
func setTarget<T: AnyObject>(target: T, action: (T) -> () -> (), controlEvent: ControlEvent) {
actions[controlEvent] = TargetActionWrapper(target: target, action: action)
}
func removeTargetForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent] = nil
}
func performActionForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent]?.performAction()
}
}
具體用法如下,在這里就不多介紹了。
class MyViewController {
let button = Control()
func viewDidLoad() {
button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .TouchUpInside)
}
func onButtonTap() {
println("Button was tapped")
}
}
好了,介紹了這么多,大家應該對柯里化理解的比較深刻了,如果你準備利用這個特點在實際項目中大展拳腳,那我還是要給你潑一盆冷水。呵呵。原因如下:
Curried function declaration syntax func foo(x: Int)(y: Int) is of limited usefulness and creates a lot of language and implementation complexity. We should remove it.
在Swift3中柯里化特性已經被移除了,原因是用處有限且增加了語言實現的復雜性。不過大家學習柯里化這個概念還是值得的,說不定在今后的工作中還會遇到。
將protocol的方法聲明為mutating
這個Tip說實在沒有什么值得說的,看過Swift基本語法應該都知道,在protocol中聲明的方法,class,struct,enum都可以實現,但是由于struct和enum是值類型,那么如果在方法中涉及成員變量修改的話,方法就需要被聲明為mutating。而對于class來說,因為它是引用類型,無論是否修改其成員都不需要將方法聲明為mutating,所以mutating對class來說相當于透明的。既然我們不知道定義的protocol方法將會被誰實現,為了避免錯誤,我們可以將protocol的方法都聲明為mutating。
Sequence
Swift中的for...in語法可以用在所有實現了SequenceType的類型上,那么SequenceType是如何實現的,我們還是看下類型聲明。首先我們可以看到
public protocol SequenceType {...}
SequenceType被聲明為一個public的protocol,其中定義了一些方法,為了能更好的理解protocol的定義,我們先來熟悉一些會看到的編譯器指令。
- @warn_unused_result:表示如果沒有檢查或者使用該方法的返回值,編譯器就會報警告。
- @noescape:用在函數的閉包參數上,意味著這個參數是唯一可被調用的(或者用在函數調用時以參數的方式出現),其意思是它的生命周期比函數調用的周期短,這有助于一些小小的性能優化,但最重要的是它屏蔽了閉包中對self.的需求。這使得函數的控制流比其他更加透明。
- throws:可以拋出一個錯誤的函數或方法必需使用 throws 關鍵字標記。這些函數和方法被稱為拋出異常函數(throwing functions)和拋出異常方法(throwing methods)。
- rethrows:一個函數或方法可以使用 rethrows 關鍵字來聲明,從而表明僅當這個函數或方法的一個函數參數拋出錯誤時這個函數或方法才拋出錯誤。這些函數和方法被稱為重拋出異常函數(rethrowing functions)和重拋出異常方法(rethrowing methods)。重拋出異常函數或方法必需有至少一個拋出異常函數參數。
好,了解了這些編譯器指令后,我們來把主要精力放在protocol具體聲明上。首先聲明如下:
associatedtype Generator : GeneratorType
從Swift2.2之后,protocol聲明中會把typealias改為associatedtype關鍵字。原因是在Swift2.2之前,都用typealias,它主要有兩種用法:
- 為一個已經存在的類型取個別名
- 在協議中作為一個類型的占位名稱
舉個例子:
protocal port {
typealias Container: SequenceType
typealias Element: Container.Generator.Element
}
如上這是兩種不同的用法,Swift2.2之后,將第一種用法改用associatedtype關鍵字。
再回到SequenceType的聲明上,首先聲明了一個Generator的占位名稱,類型是GeneratorType。從GeneratorType的聲明中,我們可以看到它封裝了枚舉狀態及遍歷Sequence的方法。去掉編譯器指令及注釋后,聲明代碼如下:
public protocol GeneratorType {
associatedtype Element
public mutating func next() -> Self.Element?
}
王巍給了一個逆向遍歷Sequence的實現GeneratorType的例子作為參考:
class ReverseGenerator: GeneratorType {
typealias Element = Int //注意這里在實現時我們用typealias
var counter: Element
init(start: Element) {
self.counter = start
}
init<T>(array: [T]) {
self.counter = array.count - 1
}
func next() -> Element? {
return counter < 0 ? nil : counter-- //注意Swift3中 -- 語法會移除
}
}
明白了GeneratorType如何實現后,我們再回頭看下SequenceType的聲明,必要實現的方法是
public protocol SequenceType {
associatedtype Generator : GeneratorType
public func generate() -> Self.Generator
}
我們以剛才定義的ReverseGenerator,來定義一個ReverseSequence。
class ReverseSequence<T>: SequenceType {
typealias Generator = ReverseGenerator
var array: [T]
init(array: [T]) {
self.array = array
}
func generate() -> Generator {
return ReverseGenerator(array: array)
}
}
這樣我們就可以把ReverseSequence用for...in語法來遍歷了,寫個例子來驗證一下
let arr = [0,1,2,3,4]
for i in ReverseSequence(array: arr) {
print("Index \(i) is \(arr[i])")
}
結果如下:
Index 4 is 4
Index 3 is 3
Index 2 is 2
Index 1 is 1
Index 0 is 0
除了支持for...in語法,ReverseSequence還可以直接使用以下方法:
public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
public func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]
原因是這些方法已經在protocol extension中實現了。
多元組(Tuple)
這個Tip也很好理解,主要還是想介紹Swift新引入的Tuple的概念。對于方法返回值,利用元組這個概念,我們可以方便的返回多個不同類型的值。但是這樣定義的方法適用于臨時組織的數據,有時候定義類或者結構作為返回值會更適合。更多關于元組的語法,大家可以參考Apple官方Swift教程。
小結
本文按書中順序介紹了4個Tips,有些概念可能比較陌生,所以本文做了很多延伸的介紹,并在下面給出了參考的鏈接。有些則展示了源碼的聲明,幫助更好理解Tip介紹的概念。 而其他一些Tips則相對簡單,基本學習過Swift語法都應該是已經掌握的,故也不多做介紹。Swift語法的學習,國內已經有愛好者無償做了高質量的翻譯,下面也給出鏈接方便大家查閱。
本人也希望把此文做成一個系列,每篇介紹幾個書中講到的Tips,再加進一些本人的理解及相關知識的拓展延伸。希望對于沒看過該書的朋友,可以學到書中所授知識,對于已讀過的朋友,也能從本文中找到一些新的角度及解讀。