Swifter 100個開發必備Tips的學習總結(一)

最近買了本王巍寫的《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,再加進一些本人的理解及相關知識的拓展延伸。希望對于沒看過該書的朋友,可以學到書中所授知識,對于已讀過的朋友,也能從本文中找到一些新的角度及解讀。

參考文章

  1. Swift:Selector 語法糖
  2. Instance Methods are Curried Functions in Swift
  3. Swift 3.0 淺析 - 即將刪除 currying 特性
  4. Swift 2.2 新特性前瞻
  5. The Swift Programming Language 中文版
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容