最近買(mǎi)了本王巍寫(xiě)的《Swifter 100個(gè)Swift2開(kāi)發(fā)必備Tip》,之前看介紹以及看了此書(shū)序言后亿乳,確定此書(shū)應(yīng)該是本不錯(cuò)的涉及Swift開(kāi)發(fā)技巧的學(xué)習(xí)材料硝拧,故決定以此書(shū)講述內(nèi)容為題材径筏,把自己對(duì)這些知識(shí)點(diǎn)的掌握以及由此引申出的知識(shí)及開(kāi)發(fā)技巧歸納總結(jié),一來(lái)便于自己鞏固障陶,二來(lái)也希望能幫到更多的喜歡Swift的開(kāi)發(fā)者滋恬。
柯里化(Currying)
相信很多朋友都跟我一樣,第一次聽(tīng)到這個(gè)名詞抱究,查了下關(guān)于該術(shù)語(yǔ)的解釋?zhuān)?/p>
在計(jì)算機(jī)科學(xué)中恢氯,柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)鼓寺。這個(gè)技術(shù)由 Christopher Strachey 以邏輯學(xué)家 Haskell Curry 命名的酿雪,盡管它是 Moses Schnfinkel 和 Gottlob Frege 發(fā)明的。
舉個(gè)Swift中柯里化的實(shí)例侄刽,如下:
func addTwoNumbers(num: Int)(another: Int) -> Int {
return num + another
}
let addThree = addTwoNumbers(3)
let result = addThree(another: 6) //result is 9
從這個(gè)例子大家應(yīng)該可以了解到柯里化在Swift中的用法,那么這個(gè)技巧在實(shí)際開(kāi)發(fā)中有什么用呢朋凉?作者給我們舉了個(gè)例子州丹,就是在Swift中Selector只能通過(guò)字符串來(lái)生成,這個(gè)在編譯期間是無(wú)法檢查的杂彭,那為了控制風(fēng)險(xiǎn)墓毒,可以采用Ole Begemann提出的利用柯里化來(lái)對(duì)Target-Action模式進(jìn)行封裝,從而可以不用Selector方式來(lái)達(dá)到目的亲怠。
相信很多朋友和我一樣所计,對(duì)這里所述內(nèi)容不是很清楚,很有可能是因?yàn)榇蠹医佑|Swift都相對(duì)比較晚团秽,這時(shí)的Swift版本已經(jīng)是2.2以后了主胧。好,我就給大家講一下Swift語(yǔ)言在這個(gè)知識(shí)點(diǎn)上的變化习勤。在Swift2.2之前踪栋,Selector需要傳入字符串,并且沒(méi)有自動(dòng)補(bǔ)全,這樣開(kāi)發(fā)者在輸入時(shí)很容易出錯(cuò)图毕。
let button = UIButton(type: .System)
button.addTarget(self, action: Selector(“buttonTapped:”), forControlEvents: .TouchUpInside)
...
func buttonTapped(sender: UIButton) { }
從Swift2.2開(kāi)始夷都,Selector寫(xiě)法更加安全了,有了自動(dòng)補(bǔ)全予颤,可以在編譯階段進(jìn)行檢查囤官。寫(xiě)法如下:
button.addTarget(self, action: #selector(ViewController.buttonTapped(_:)), forControlEvents: .TouchUpInside)
好了,了解了Swift語(yǔ)言Selector語(yǔ)法的變化蛤虐,我們?cè)倩貋?lái)看Ole Begemann利用柯里化來(lái)對(duì)Target-Action模式進(jìn)行的封裝党饮。首先給出了Swift中實(shí)例方法的本質(zhì)
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.
簡(jiǎn)單翻譯就是:Swift中的實(shí)例方法就是一個(gè)類(lèi)型方法,它以實(shí)例作為方法的第一個(gè)參數(shù)笆焰,并返回一個(gè)應(yīng)用于該實(shí)例的函數(shù)劫谅。對(duì)這個(gè)概念也舉了個(gè)例子如下:
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常量可以看做是類(lèi)似C語(yǔ)言中實(shí)例方法的函數(shù)指針,也可以看做
let depositor: BankAccount -> (Double) -> ()
也就是說(shuō),這個(gè)函數(shù)有一個(gè)參數(shù)捏检,就是BankAccount的實(shí)例荞驴,返回值為另一個(gè)函數(shù),該函數(shù)接受一個(gè)Double類(lèi)型的參數(shù)贯城,沒(méi)有返回值熊楼。
BankAccount.deposit(account)(100) // balance is now 300
我們?nèi)绻B起來(lái),再回頭看開(kāi)頭介紹的實(shí)例方法的概念能犯,就應(yīng)該理解的更清楚了鲫骗。
好明白了概念,我們?cè)賮?lái)看它是如何不依賴(lài)OC的消息分發(fā)機(jī)制(Selector方式)踩晶,而用純Swift的方式實(shí)現(xiàn)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")
}
}
好了术吝,介紹了這么多,大家應(yīng)該對(duì)柯里化理解的比較深刻了茸苇,如果你準(zhǔn)備利用這個(gè)特點(diǎn)在實(shí)際項(xiàng)目中大展拳腳排苍,那我還是要給你潑一盆冷水。呵呵学密。原因如下:
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中柯里化特性已經(jīng)被移除了淘衙,原因是用處有限且增加了語(yǔ)言實(shí)現(xiàn)的復(fù)雜性。不過(guò)大家學(xué)習(xí)柯里化這個(gè)概念還是值得的腻暮,說(shuō)不定在今后的工作中還會(huì)遇到彤守。
將protocol的方法聲明為mutating
這個(gè)Tip說(shuō)實(shí)在沒(méi)有什么值得說(shuō)的,看過(guò)Swift基本語(yǔ)法應(yīng)該都知道哭靖,在protocol中聲明的方法遗增,class,struct款青,enum都可以實(shí)現(xiàn)做修,但是由于struct和enum是值類(lèi)型,那么如果在方法中涉及成員變量修改的話(huà)抡草,方法就需要被聲明為mutating饰及。而對(duì)于class來(lái)說(shuō),因?yàn)樗且妙?lèi)型康震,無(wú)論是否修改其成員都不需要將方法聲明為mutating燎含,所以mutating對(duì)class來(lái)說(shuō)相當(dāng)于透明的。既然我們不知道定義的protocol方法將會(huì)被誰(shuí)實(shí)現(xiàn)腿短,為了避免錯(cuò)誤屏箍,我們可以將protocol的方法都聲明為mutating绘梦。
Sequence
Swift中的for...in語(yǔ)法可以用在所有實(shí)現(xiàn)了SequenceType的類(lèi)型上,那么SequenceType是如何實(shí)現(xiàn)的赴魁,我們還是看下類(lèi)型聲明卸奉。首先我們可以看到
public protocol SequenceType {...}
SequenceType被聲明為一個(gè)public的protocol,其中定義了一些方法颖御,為了能更好的理解protocol的定義榄棵,我們先來(lái)熟悉一些會(huì)看到的編譯器指令。
- @warn_unused_result:表示如果沒(méi)有檢查或者使用該方法的返回值潘拱,編譯器就會(huì)報(bào)警告疹鳄。
- @noescape:用在函數(shù)的閉包參數(shù)上,意味著這個(gè)參數(shù)是唯一可被調(diào)用的(或者用在函數(shù)調(diào)用時(shí)以參數(shù)的方式出現(xiàn))芦岂,其意思是它的生命周期比函數(shù)調(diào)用的周期短瘪弓,這有助于一些小小的性能優(yōu)化,但最重要的是它屏蔽了閉包中對(duì)self.的需求禽最。這使得函數(shù)的控制流比其他更加透明杠茬。
- throws:可以?huà)伋鲆粋€(gè)錯(cuò)誤的函數(shù)或方法必需使用 throws 關(guān)鍵字標(biāo)記。這些函數(shù)和方法被稱(chēng)為拋出異常函數(shù)(throwing functions)和拋出異常方法(throwing methods)弛随。
- rethrows:一個(gè)函數(shù)或方法可以使用 rethrows 關(guān)鍵字來(lái)聲明,從而表明僅當(dāng)這個(gè)函數(shù)或方法的一個(gè)函數(shù)參數(shù)拋出錯(cuò)誤時(shí)這個(gè)函數(shù)或方法才拋出錯(cuò)誤。這些函數(shù)和方法被稱(chēng)為重拋出異常函數(shù)(rethrowing functions)和重拋出異常方法(rethrowing methods)宁赤。重拋出異常函數(shù)或方法必需有至少一個(gè)拋出異常函數(shù)參數(shù)舀透。
好,了解了這些編譯器指令后决左,我們來(lái)把主要精力放在protocol具體聲明上愕够。首先聲明如下:
associatedtype Generator : GeneratorType
從Swift2.2之后,protocol聲明中會(huì)把typealias改為associatedtype關(guān)鍵字佛猛。原因是在Swift2.2之前惑芭,都用typealias,它主要有兩種用法:
- 為一個(gè)已經(jīng)存在的類(lèi)型取個(gè)別名
- 在協(xié)議中作為一個(gè)類(lèi)型的占位名稱(chēng)
舉個(gè)例子:
protocal port {
typealias Container: SequenceType
typealias Element: Container.Generator.Element
}
如上這是兩種不同的用法继找,Swift2.2之后遂跟,將第一種用法改用associatedtype關(guān)鍵字。
再回到SequenceType的聲明上婴渡,首先聲明了一個(gè)Generator的占位名稱(chēng)幻锁,類(lèi)型是GeneratorType。從GeneratorType的聲明中边臼,我們可以看到它封裝了枚舉狀態(tài)及遍歷Sequence的方法哄尔。去掉編譯器指令及注釋后,聲明代碼如下:
public protocol GeneratorType {
associatedtype Element
public mutating func next() -> Self.Element?
}
王巍給了一個(gè)逆向遍歷Sequence的實(shí)現(xiàn)GeneratorType的例子作為參考:
class ReverseGenerator: GeneratorType {
typealias Element = Int //注意這里在實(shí)現(xiàn)時(shí)我們用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中 -- 語(yǔ)法會(huì)移除
}
}
明白了GeneratorType如何實(shí)現(xiàn)后柠并,我們?cè)倩仡^看下SequenceType的聲明岭接,必要實(shí)現(xiàn)的方法是
public protocol SequenceType {
associatedtype Generator : GeneratorType
public func generate() -> Self.Generator
}
我們以剛才定義的ReverseGenerator富拗,來(lái)定義一個(gè)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語(yǔ)法來(lái)遍歷了鸣戴,寫(xiě)個(gè)例子來(lái)驗(yàn)證一下
let arr = [0,1,2,3,4]
for i in ReverseSequence(array: arr) {
print("Index \(i) is \(arr[i])")
}
結(jié)果如下:
Index 4 is 4
Index 3 is 3
Index 2 is 2
Index 1 is 1
Index 0 is 0
除了支持for...in語(yǔ)法啃沪,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]
原因是這些方法已經(jīng)在protocol extension中實(shí)現(xiàn)了。
多元組(Tuple)
這個(gè)Tip也很好理解葵擎,主要還是想介紹Swift新引入的Tuple的概念谅阿。對(duì)于方法返回值,利用元組這個(gè)概念酬滤,我們可以方便的返回多個(gè)不同類(lèi)型的值签餐。但是這樣定義的方法適用于臨時(shí)組織的數(shù)據(jù),有時(shí)候定義類(lèi)或者結(jié)構(gòu)作為返回值會(huì)更適合盯串。更多關(guān)于元組的語(yǔ)法氯檐,大家可以參考Apple官方Swift教程。
小結(jié)
本文按書(shū)中順序介紹了4個(gè)Tips体捏,有些概念可能比較陌生冠摄,所以本文做了很多延伸的介紹,并在下面給出了參考的鏈接几缭。有些則展示了源碼的聲明河泳,幫助更好理解Tip介紹的概念。 而其他一些Tips則相對(duì)簡(jiǎn)單年栓,基本學(xué)習(xí)過(guò)Swift語(yǔ)法都應(yīng)該是已經(jīng)掌握的拆挥,故也不多做介紹。Swift語(yǔ)法的學(xué)習(xí)某抓,國(guó)內(nèi)已經(jīng)有愛(ài)好者無(wú)償做了高質(zhì)量的翻譯纸兔,下面也給出鏈接方便大家查閱。
本人也希望把此文做成一個(gè)系列否副,每篇介紹幾個(gè)書(shū)中講到的Tips汉矿,再加進(jìn)一些本人的理解及相關(guān)知識(shí)的拓展延伸。希望對(duì)于沒(méi)看過(guò)該書(shū)的朋友备禀,可以學(xué)到書(shū)中所授知識(shí)洲拇,對(duì)于已讀過(guò)的朋友,也能從本文中找到一些新的角度及解讀曲尸。