Swifter 100個(gè)開(kāi)發(fā)必備Tips的學(xué)習(xí)總結(jié)(一)

最近買(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ò)的朋友,也能從本文中找到一些新的角度及解讀曲尸。

參考文章

  1. Swift:Selector 語(yǔ)法糖
  2. Instance Methods are Curried Functions in Swift
  3. Swift 3.0 淺析 - 即將刪除 currying 特性
  4. Swift 2.2 新特性前瞻
  5. The Swift Programming Language 中文版
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呻待,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子队腐,更是在濱河造成了極大的恐慌蚕捉,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柴淘,死亡現(xiàn)場(chǎng)離奇詭異迫淹,居然都是意外死亡秘通,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)敛熬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肺稀,“玉大人,你說(shuō)我怎么就攤上這事应民』霸” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵诲锹,是天一觀的道長(zhǎng)繁仁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)归园,這世上最難降的妖魔是什么黄虱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮庸诱,結(jié)果婚禮上捻浦,老公的妹妹穿的比我還像新娘。我一直安慰自己桥爽,他們只是感情好朱灿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著钠四,像睡著了一般盗扒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上形导,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音习霹,去河邊找鬼朵耕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛淋叶,可吹牛的內(nèi)容都是我干的阎曹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼煞檩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼处嫌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起斟湃,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤熏迹,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后凝赛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體注暗,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坛缕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捆昏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赚楚。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖骗卜,靈堂內(nèi)的尸體忽然破棺而出宠页,到底是詐尸還是另有隱情,我是刑警寧澤寇仓,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布举户,位于F島的核電站,受9級(jí)特大地震影響焚刺,放射性物質(zhì)發(fā)生泄漏敛摘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一乳愉、第九天 我趴在偏房一處隱蔽的房頂上張望兄淫。 院中可真熱鬧,春花似錦蔓姚、人聲如沸捕虽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)泄私。三九已至晌端,卻和暖如春咧纠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背演痒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工器虾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留端姚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓昏鹃,卻偏偏與公主長(zhǎng)得像阅嘶,于是被迫代替她去往敵國(guó)和親讯柔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容