深入剖析swift函數(shù)式編程

本文適合哪些人?

本文針對的是已經(jīng)有一部分Swift開發(fā)的基礎(chǔ),同時對函數(shù)式范式比較感興趣的開發(fā)者识啦。 當然,如果只對函數(shù)式范式感興趣神妹,我覺得這篇文章也值得一看颓哮。

函數(shù)式編程是什么?

首先來看這個詞語”Functional Programming“鸵荠,它是什么冕茅?

當需要去查一個專業(yè)術(shù)語的定義的時候,我的第一反應是來查詢Wikipedia:

?

In computer science, fucnitonal programming is a programming paradigm where programs are constructed by applying and composing fucntions.

在這個定義里蛹找,有一個很熟悉的詞——programming paradigm, 一般翻譯為編程范式姨伤,可是我對這個翻譯還是有些迷糊,于是我又在wikipedia中查找這個詞語的含義:

?

Programming paradigms are a way to classify programming languages based on their features.

編程范式(編程范例)是一種基于語言自身的特性來給編程語言分類的方式庸疾。

同時wikipedia中還總結(jié)了常見的編程范式的分類:

  • imperative
    • procedural
    • object-oriented
  • declarative
    • functional
    • logic
    • mathematical
    • reactive

那么究竟什么是編程范式呢乍楚?我們知道編程是一門工程學,它的目的是去解決問題届慈,而解決問題可以有很多的方法徒溪,編程范例就是代表著解決問題的不同思路。如果說我們是編程世界的造物主的話金顿,那么編程范例應該就是我們創(chuàng)造這個世界的方法論词渤。所以我非常喜歡臺灣那邊對programming paradigm 的翻譯:程式設(shè)計法。

為什么我要強調(diào)編程范例是什么東西串绩,而且還分門別類的列舉了出來這些編程范例呢缺虐?

因為編程本身是抽象的,編程范例其實就是我們?nèi)绾纬橄筮@個世界的方法礁凡,我只是想通過這個具體的定義來說明函數(shù)式本身就是一種方法論高氮。 所以我們學習的時候沒必要害怕它,遇到引用透明顷牌,副作用剪芍,科里化,函子窟蓝,單子罪裹,惰性求值等等等等這些概念的時候,畏懼的原因只是不熟悉而已,就想我們學習面向?qū)ο蟮臅r候:繼承状共,封裝套耕,多態(tài),動態(tài)綁定峡继,消息傳遞等等等等冯袍,這些概念我們一開始也不熟悉,所以當我們熟悉了函數(shù)式這些概念的時候碾牌,一切自然水到渠成康愤。 在我們熟悉的面向?qū)ο蟮木幊谭妒街校覀冎浪乃枷胧牵?strong>一切皆對象舶吗,而在純函數(shù)式的編程范式中征冷,可以說:一切皆函數(shù)。在函數(shù)式編程中誓琼,函數(shù)是一等公民资盅,那什么是一等公民呢?就是它可以作為參數(shù)踊赠,返回值呵扛,也可以賦值給變量,也就是說它的地位其實是和Int筐带,String, Double等基本類型是一樣的今穿,換言之,要像使用基本類型一樣去使用它伦籍!

不同的思想就是創(chuàng)建世界的方法論的不同之處蓝晒,這里我舉個例子,那就是狀態(tài)帖鸦,比如登錄的各種狀態(tài)芝薇,維護狀態(tài)會大大增加系統(tǒng)的復雜性,特別是狀態(tài)很多的時候作儿,而且引入狀態(tài)這個概念之后洛二,會帶來很多復雜的問題:狀態(tài)持久化,環(huán)境模型等等等攻锰,而如果使用面向?qū)ο蟮木幊谭独浪唬梢詫?strong>每一個狀態(tài)都定義為一個對象,如C#中的狀態(tài)機的實現(xiàn)娶吞,而在函數(shù)式編程里呢垒迂? 在SICP中提到,狀態(tài)是隨著時間改變的妒蛇,所以狀態(tài)是否可以使用f(t)來表示呢机断?這就是使用函數(shù)式的思路來抽象狀態(tài)楷拳。

當然,我這里并不是說只能使用一種編程范式吏奸,我也并不鼓吹函數(shù)式就一直是好的欢揖,但是掌握函數(shù)式可以讓我們在解決問題的時候提供更多的選擇,更有效率的解決問題苦丁,事實上浸颓,我們解決問題(創(chuàng)造世界)肯定會使用很多種方法論即多種編程范式物臂,一般情況下旺拉,更現(xiàn)代的編程語言都支持多范式編程,這里用swift里的RxSwift來舉例:

public class Observable<Element> : ObservableType {
    internal init()
    
    public func subscribe<Observer>(_ observer: Observer) -> Disposable where Element == Observer.Element, Observer : RxSwift.ObserverType

    public func asObservable() -> Observable<Element>
}

// 觀察者
final internal class AnonymousObserver<Element> : ObserverBase<Element> {

    internal typealias EventHandler = (Event<Element>) -> Void

    internal init(_ eventHandler: @escaping EventHandler)

    override internal func onCore(_ event: Event<Element>)
}



extension ObservableType {
    public func flatMap<Source>(_ selector: @escaping (Element) throws -> Source) -> Observable<Source.Element> where Source : RxSwift.ObservableConvertibleType
}

extension ObservableType {
    public func map<Result>(_ transform: @escaping (Element) throws -> Result) -> Observable<Result>
}

它的Observable和Observer都抽象成了類棵磷,并且添加了相應的行為蛾狗,承擔了相應的職責,這是面向?qū)ο蠓妒?/strong>仪媒;它實現(xiàn)了OberveableType協(xié)議沉桌,并且拓展了該協(xié)議,添加了大量的默認實現(xiàn)算吩,這是面向協(xié)議范式留凭;它實現(xiàn)了map,和flatMap方法偎巢,可以說Observable是一個函數(shù)單子(Monad)蔼夜,同時也提供了大量的操作符可供使用和組合,這是函數(shù)式范式压昼;同時求冷,總所周知,Reactive框架是一個響應式的框架窍霞,所以它也是響應式范式......

更何況匠题,編程能力不就是抽象能力的體現(xiàn)嗎?所以我認為掌握函數(shù)式是非常必要的但金!那么具體來說為什么重要呢韭山?

在1984年的時候,John Hughes 有一篇很著名的論文《Why Functional Programming Matters》, 它解答了我們的疑問冷溃。

為什么函數(shù)式編程重要掠哥?

通常網(wǎng)絡(luò)上的一些文章都會總結(jié)它的優(yōu)點:它沒有賦值,沒有副作用秃诵,沒有控制流等等等等续搀,不同的只是它們對于各個關(guān)鍵詞諸如引用透明,無副作用的種種解釋菠净,單是這只是列出了很多函數(shù)式程序 "沒有" 什么禁舷,卻沒有說它 “有” 什么彪杉,所以這些優(yōu)點其實沒有太大的說服力。而且我們實際上去寫程序的時候牵咙,也不可能特意去寫一個 缺少了賦值語句或者特別引用透明的程序派近,這也不是衡量質(zhì)量的尺度,那么真正重要的是什么呢洁桌?

在這篇論文中提到渴丸,模塊化設(shè)計是成功的程序化設(shè)計的關(guān)鍵,這一觀點已經(jīng)被普遍接受了另凌,但有一點經(jīng)常容易被忽略谱轨,那就是編寫一個模塊化程序解決問題的時候,程序員首先要把問題分解為子問題吠谢,然后解決這些子問題并把解決方案合并土童。程序員能夠以什么方式分解問題,直接取決于他能以什么方式把解決方案粘起來工坊。而函數(shù)式范式其實提供給我們非常重要的粘合劑献汗,它可以讓我們設(shè)計一些更小、更簡潔王污、更通用的模塊罢吃,同時使用黏合劑粘合起來。

那么它提供了哪些黏合劑呢昭齐?這篇論文介紹了兩種:

黏合函數(shù):高階函數(shù)

?

The first of the two new kinds of glue enables simple functions to be glued together to make more complex ones.

黏合簡單的函數(shù)變?yōu)楦鼜碗s的函數(shù)琅坡。這樣的好處是我們模塊化的顆粒度是更細的书蚪,可以組合的復雜函數(shù)也是更多的。如果非要做一個比喻的話,我覺得就像樂高的基礎(chǔ)組件:

swift

這種聚合就是一個泛化的高階函數(shù)和一些特化函數(shù)的聚合吝岭,這樣的高階函數(shù)一旦定義古戴,很多操作都可以很容易地編寫出來田弥。

黏合程序:惰性求值

?

The other new kind of glue that functional languages provide enables whole programs to be glued together.

函數(shù)式語言提供的另一種黏合劑就是可以使得程序黏在一起逞带。假設(shè)有這么一個函數(shù):

g(f(input))

傳統(tǒng)上,需要先計算f租谈,然后再計算g篮奄,這是通過將f的輸出存儲在臨時文件中實現(xiàn)的,這種方法的問題是臨時文件會占用太大的空間割去,會讓程序之間的黏合變得不太現(xiàn)實窟却。而函數(shù)式語言提供的這一種解決方案,程序f和g嚴格的同步運行呻逆,只有當g視圖讀取輸入時夸赫,f才啟動。這種求值方式盡可能得少運行咖城,因此被稱為 "惰性求值" 茬腿。它將程序模塊化為一個產(chǎn)生大量可能解的生成器與一個選取恰當解的選擇器的方案變得可行握础。

大家如果有時間還是應該去讀讀這一篇論文,在論文中,它講述了三個實例:牛頓-拉夫森求根法亥至,數(shù)值微分絮供,數(shù)值積分壤靶,以及啟發(fā)性搜索恬惯,并使用函數(shù)式來實現(xiàn)它們,非常的精彩,這里我就不復述這些實例了晴圾。最后我再引用一下該論文的結(jié)論:

?

在本文中,我們指出模塊化是成功的程序設(shè)計的關(guān)鍵。以提高生產(chǎn)力為目標的程序語言,必須良好地支持模塊化程序設(shè)計。但是,新的作用域規(guī)則和分塊編譯的技巧是不夠的——“模塊化”不僅僅意味著“模塊”。我們分解程序的能力直接取決于將解決方案粘在一起的能力驻右。為了協(xié)助模塊化程序設(shè)計,程序語言必須提供優(yōu)良的黏合劑森爽。函數(shù)式程序語言提供了兩種新的黏合劑——高階函數(shù)與惰性求值祈惶。

一顆棗樹(例子)

這個例子我參考了Objc.io的《函數(shù)式Swift》書籍中關(guān)于如何使用函數(shù)式的方式來封裝濾鏡的案例。

Core Image是一很強大的圖像處理框架,但是它的API是弱類型的 —— 可以通過鍵值編碼來配置圖像濾鏡克蚂,這樣就導致很容易出錯,所以可以使用類型來避免這些原因?qū)е碌倪\行時錯誤,什么意思呢?就是說我們可以封裝一些基礎(chǔ)的濾鏡Filter, 并且還可以實現(xiàn)它們之間的聚合方式栈顷。這就是上述論文中介紹的函數(shù)式編程提供的黏合劑之一:使簡單的函數(shù)可以聚合起來形成復雜的函數(shù)蛙卤。

首先確定我們的濾鏡類型,該函數(shù)應該接受一個圖像作為參數(shù)并返回一個新的圖像:

typalias Filter = (CIImage) -> CIImage

在這里引用一段書中的原話:

?

我們應該謹慎地選擇類型噩死。這比其他任何事情都重要颤难,因為類型將左右開發(fā)流程。

然后可以開始定義函數(shù)來構(gòu)件特定的基礎(chǔ)濾鏡了:

/// sobel提取邊緣濾鏡
func sobel() -> Filter {
    return { image in
        let sobel: [CGFloat] = [-1, 0, 1, -2, 0, 2, -1, 0, 1]
        let weight = CIVector(values: sobel, count: 9)
        guard let filter = CIFilter(name: "CIConvolution3X3",
                                    parameters: [kCIInputWeightsKey: weight,
                                                 kCIInputBiasKey: 0.5,
                                                 kCIInputImageKey: image]) else { fatalError() }
        
        guard let outImage = filter.outputImage else { fatalError() }
        
        return outImage.cropped(to: image.extent)
    }
}

/// 顏色反轉(zhuǎn)濾鏡
func colorInvert() -> Filter {
    return { image in
        guard let filter = CIFilter(name: "CIColorInvert",
                                    parameters: [kCIInputImageKey: image]) else { fatalError() }
        guard let outImage = filter.outputImage else { fatalError() }
        return outImage.cropped(to: image.extent)
    }
}


/// 顏色變色濾鏡
func colorControls(h: NSNumber, s: NSNumber, b: NSNumber) -> Filter {
    return { image in
        guard let filter = CIFilter(name: "CIColorControls", parameters: [kCIInputImageKey: image, kCIInputSaturationKey: h, kCIInputContrastKey: s, kCIInputBrightnessKey: b]) else { fatalError() }
        
        guard let outImage = filter.outputImage else { fatalError() }
        
        return outImage.cropped(to: image.extent)
    }
}

直接黏合

基礎(chǔ)組件已經(jīng)有了已维,接下來就可以堆積木了行嗤。如果有一個濾鏡需要:先提取邊緣 -> 顏色反轉(zhuǎn) -> 顏色變色,那么我們可以實現(xiàn)如下:

let newFilter: Filter = { image in
    return colorControls(h: 97, s: 8, b: 85)(colorInvert()(sobel()(image)))
}

上述做法有一些問題:

  • 可讀性差:無法代碼即注釋垛耳,無法很容易的知道濾鏡的執(zhí)行順序
  • 不易拓展:API不友好栅屏,添加新的濾鏡時,需要考慮順序和括號堂鲜,很容易出錯

自定義函數(shù)黏合

首先我們解決可讀性差的問題栈雳,因為直接使用嵌套調(diào)用方法,所以會可讀性差缔莲。所以我們要避免嵌套調(diào)用哥纫,直接定義combine方法來組合濾鏡:

func compose(filter filter1: @escaping Filter, with filter2: @escaping Filter) -> Filter {
    return { image in
        filter2(filter1(image))
    }
}

// sobel -> invertColor
let newFilter1: Filter = compose(sobel(), colorInvert()) // 左結(jié)合的

這是左結(jié)合的,所以可讀性是OK的痴奏,但是如果有三個濾鏡組合呢蛀骇?四個濾鏡組合呢厌秒?要定義那么多方法嗎? 巧了擅憔,還真有人是這么干的:

swift

如果大家去看RxSwift的話鸵闪,就會看見它組合多個Observable的函數(shù): zip , combineLastest ,每一個方法簇都提供了支持多個參數(shù)的組合方法暑诸,可是這就意味著我們在這個案例也是可以這樣做的岛马,但是這顯然不是最好的解決方案。

如果使用combine這里三個濾鏡組合的方案:

let newFilter2: Filter = compose(compose(sobel(), colorInvert()), colorControls(h:97, s:8, b:85)))

可讀性還行屠列,但是還是在添加新的濾鏡的時候容易出錯啦逆,不那么容易拓展。如果要再組合多個濾鏡笛洛,那么就需要多個combine函數(shù)嵌套調(diào)用夏志。

自定義操作符黏合

如果對應到數(shù)學領(lǐng)域的話,其實這幾個濾鏡的組合不就是四則運算中的 + 嗎苛让?一層一層效果的疊加沟蔑,當然,確切地說狱杰,從效果上和 + 更相似瘦材,但是從特性來說更符合減法 -的,都是向左結(jié)合仿畸,而且都不滿足交換律食棕。

所以我們可以自定義操作符來處理濾鏡的結(jié)合:

infix operator >>>
func >>>(filter1: @escaping Filter, filter2: @escaping Filter) -> Filter {
    return { image in
        filter2(filter1(image))
    }
}

當然還有一個小問題,就是如果有三個濾鏡組合的話错沽,會報錯簿晓,因為我們沒有指定它組合的方式(左結(jié)合,還是右結(jié)合)所以這里我們讓它繼承加法的優(yōu)先級千埃,因為它和加法一樣都是左結(jié)合的:

infix operator >>>: AdditionPrecedence // 讓它繼承+操作符的優(yōu)先級, 左結(jié)合
func >>>(filter1: @escaping Filter, filter2: @escaping Filter) -> Filter {
    return { image in
        filter2(filter1(image))
    }
}

那接下來我們愉快地使用它吧:

let filter = sobel() >>> colorInvert() >>> colorControls(h: 97, s: 8, b: 85)
let outputImage = filter(inputImage)
imageView.image = UIImage(ciImage: outputImage)
swift

函數(shù)式Swift.001.jpeg

那么這里來總結(jié)一下這一波過程憔儿,假設(shè)需求是存在的:

我們定義了很多基礎(chǔ)濾鏡層(Filter),接下來肯定需要組合基礎(chǔ)濾鏡為我們實際需求需要的濾鏡放可,有的濾鏡可能是有三個基礎(chǔ)濾鏡組合的谒臼,有的需要五個基礎(chǔ)濾鏡組合,當然極限情況下耀里,可能還有需要十個濾鏡組合的蜈缤。

所以我們需要定義不同濾鏡組合的黏合函數(shù), 我們一共經(jīng)歷了三個組合方案的變遷:

  1. 直接組合
  2. 定義compose函數(shù)
  3. 自定義操作符

當然备韧,諸君也可以使用更好的組合方案劫樟,如果可以希望留個言,共同探討探討。

還有一顆也是棗樹(例子)

接下來這個例子叠艳,是一個我們使用Objective-C編程的時候經(jīng)常會遇到的問題奶陈,需求如下:第二行數(shù)據(jù)必須等待第一行請求結(jié)束之后才可以開始請求。

swift

那么開始吧附较!

首先我們來看最容易的實現(xiàn)方案:

    @objc func syncData() {
        self.statusLabel.text = "正在同步火影忍者數(shù)據(jù)"
        
        WebAPI.requestNaruto { (firstResult) in
            if case .success(let result) = firstResult {
                self.sectionOne = result.map { $0 as? String ?? "" }
                DispatchQueue.main.async {
                    self.tableView.reloadSections([0], with: .automatic)
                    
                    self.statusLabel.text = "正在同步海賊王數(shù)據(jù)"
                    WebAPI.requestOnePiece { (secondResult) in
                        if case Result.success(let result) = secondResult {
                            self.sectionTwo = result.map { $0 as? String ?? "" }
                            DispatchQueue.main.async {
                                self.statusLabel.text = "同步海賊王數(shù)據(jù)成功"
                                self.tableView.reloadSections([1], with: .automatic)
                            }
                        }
                    }
                }
            }
        }
    }

熟悉嗎吃粒?當然熟悉,直接在第一個請求的callback中直接進行第二個請求拒课,但是請注意徐勃,這和OC寫的有區(qū)別嗎?我們這樣和寫和簡單的人肉翻譯機有區(qū)別嗎早像?我們寫的是Swift這個多范式的編程語言嗎僻肖?

回到例子,我們就事論事卢鹦,我覺得這樣寫會有幾個問題:

  1. 數(shù)據(jù)修改和UI修改耦合在了一起
  2. 多重嵌套
  3. 違背了OCP(Open Closed Principle)法則:應該對修改閉合臀脏,對拓展開放
  4. 丑!

解決數(shù)據(jù)和UI耦合

從重要性的角度冀自,我覺得應該先解決第4個問題揉稚,但是出于節(jié)奏,我們還是從第一個問題開始解決吧~

    @objc func syncDataThere() {
        // 嵌套函數(shù)
        func updateStatus(text: String, reload: (isReload: Bool, section: Int)) {
            DispatchQueue.main.async {
                self.statusLabel.text = text
                if reload.isReload { self.tableView.reloadSections([reload.section], with: .automatic) }
            }
        }
        
        updateStatus(text: "正在同步火影忍者數(shù)據(jù)", reload: (false, 0))
        
        requestNaruto {
            updateStatus(text: "正在同步海賊王數(shù)據(jù)", reload: (true, 0))
            self.requestOnePiece {
                updateStatus(text: "同步數(shù)據(jù)成功", reload: (true, 1))
            }
        }
    }

這里我把網(wǎng)絡(luò)請求和數(shù)據(jù)處理都封裝到了網(wǎng)絡(luò)請求中熬粗,而且使用了swift的特性:嵌套函數(shù)搀玖,剝離了一部分重復代碼,這樣整個請求就變得非常清晰明了了驻呐,而且數(shù)據(jù)和UI就隔離開來了灌诅,并沒有耦合在一起。

可是嵌套的問題還是存在暴氏,如何解決呢延塑?

解決多重嵌套

還記得我介紹的第一棵棗樹嗎?我使用了自定義操作符來解決了函數(shù)調(diào)用的嵌套答渔,這里其實也是一樣的思路,但是要更復雜些侥涵。

這里我還需要重復引用一下《函數(shù)式Swift》中的那句話:

?

我們應該謹慎地選擇類型沼撕。這比其他任何事情都重要,因為類型將左右開發(fā)流程芜飘。

第一步抽象

這里有兩個類型需要抽象务豺,第一是執(zhí)行單個語句的函數(shù)(這里是更新UI),第二個是對應網(wǎng)絡(luò)請求的函數(shù)

infix operator ->> AdditionPrecedence
typealias Action = () -> Void
typealias Request = (@escaping Action) -> Void

第二步抽象

那么如何將原來的函數(shù)拆解為使用類型表示的函數(shù)呢嗦明?

func syncDataF() {
    ......
 requestNaruto {
     updateStatus(text: "正在同步海賊王數(shù)據(jù)", reload: (true, 0))
        self.requestOnePiece {
         updateStatus(text: "同步數(shù)據(jù)成功", reload: (true, 1))
        }
 }
)

我們由上往下笼沥,那么抽象的過程應該就是

  • (Request, Action) -> Request

第一個請求 和 回調(diào)中的第一個Action,但是第一個請求還沒有結(jié)束,所以返回的還是Request

  • (Request, Request) -> Request

處理了第一個Action的第一請求 + 第二個請求, 但是請求還是沒有結(jié)束奔浅,所以返回的還是Request

  • (Request, Action) -> Action

第二個請求加上最后需要處理的Action , 完畢馆纳!

所以結(jié)果如下:

@objc func syncDataFour() {
 func updateStatus(text: String, reload: (isReload: Bool, section: Int)) {
      DispatchQueue.main.async {
         self.statusLabel.text = text
            if reload.isReload { 
                self.tableView.reloadSections([reload.section], with: .automatic) 
            }
        }
    }
    updateStatus(text: "正在同步火影忍者數(shù)據(jù)", reload: (false, 0))
    // 我們來拆解一下函數(shù):要把函數(shù)抽象出來,這一點非常的重要
    // (Request, Action) -> Request
    // (Request, Request) -> Request
    // (Request, Action) -> Action
    // 通過這樣的拆解方式就可以開始定義方法了
    let task: Action =
      requestNaruto
            ->> { updateStatus(text: "正在同步海賊王數(shù)據(jù)", reload: (true, 0)) }
            ->> requestOnePiece
            ->> { updateStatus(text: "同步數(shù)據(jù)成功", reload: (true, 1)) }
    task()
}

結(jié)果呢汹桦?我解決了嵌套的問題鲁驶,很好,很完美舞骆,可是也很天真钥弯。

解決OCP問題

即使我們使用了自定義操作符,也沒有解決OCP問題督禽,因為如果我們要添加請求的話脆霎,我們還是需要修改原來的方法,依然違背了OCP法則狈惫。

那么怎么解決呢绪穆?

嗯嗯,具體的虱岂,請各位自己去試驗吧玖院!

我在文章尾部添加了相應的引用信息,這個例子是基于2016年的國內(nèi)的Swift大會中翁陽的分享《Swift, 改善既有代碼的設(shè)計》第岖,如果有時間难菌,希望大家可以去看看這個分享。

在分享中蔑滓,他使用了面向協(xié)議的思路解決了OCP問題郊酒,很抽象,很精彩键袱。

總結(jié)

很開心諸位看到了這里燎窘,我覺得這篇文章的能量密度應該不會浪費你們的時間。

在這邊文章中蹄咖,我首先是追問了函數(shù)式編程褐健,以及編程范式的定義,只是想告訴大家:函數(shù)式編程之所以復雜只是因為我們不熟悉澜汤,同時它也應該是我們必須的工具蚜迅。

然后我介紹了《Why Functional Programming Matters》這篇論文,它說明了為什么函數(shù)式編程重要俊抵,提到函數(shù)式范式的兩大武器:高階函數(shù)和惰性求值谁不。

最后我使用了兩顆棗樹來給大家看一看Swift語言結(jié)合函數(shù)式的思想可以有哪些奇妙的化學反應。

那么這一次Swift的一次函數(shù)式之旅就結(jié)束了徽诲。但是還是想補充幾句刹帕,每一年的WWDC其實Swift都更新了很多的內(nèi)容吵血,Swift本身也一直在增加新的特性,一直在穩(wěn)健的迭代著偷溺,如果我們還是使用Objective-C的思維去寫Swift的話蹋辅,其實本身是落后于語言發(fā)展的。

最后引用王安石的《游褒禪山記》中的一段話:

?

而世之奇?zhèn)ネ鋈亍⒐骞衷未洌浅V^,常在于險遠砍濒,而人之所罕至焉淋肾,故非有志者不能至也。

與君共勉爸邢!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末樊卓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子杠河,更是在濱河造成了極大的恐慌碌尔,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件券敌,死亡現(xiàn)場離奇詭異唾戚,居然都是意外死亡,警方通過查閱死者的電腦和手機待诅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門叹坦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卑雁,你說我怎么就攤上這事募书。” “怎么了测蹲?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵莹捡,是天一觀的道長。 經(jīng)常有香客問我扣甲,道長篮赢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任文捶,我火速辦了婚禮荷逞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粹排。我一直安慰自己,他們只是感情好涩澡,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布顽耳。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪射富。 梳的紋絲不亂的頭發(fā)上膝迎,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音胰耗,去河邊找鬼限次。 笑死,一個胖子當著我的面吹牛柴灯,可吹牛的內(nèi)容都是我干的卖漫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赠群,長吁一口氣:“原來是場噩夢啊……” “哼羊始!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起查描,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤突委,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冬三,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匀油,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年勾笆,在試婚紗的時候發(fā)現(xiàn)自己被綠了敌蚜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡匠襟,死狀恐怖钝侠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酸舍,我是刑警寧澤帅韧,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站啃勉,受9級特大地震影響忽舟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜淮阐,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一叮阅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泣特,春花似錦浩姥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兜挨。三九已至,卻和暖如春眯分,著一層夾襖步出監(jiān)牢的瞬間拌汇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工弊决, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留噪舀,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓飘诗,卻偏偏與公主長得像与倡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疚察,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

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