譯 Swift Talk -- NetWorking

原文來自于 objc.io

Transcript

0:01 我們來討論下 Swift talk app 的網(wǎng)絡(luò)層邻吞。我們認(rèn)為這是個有趣的例子因為設(shè)計與之前的 Objective-C 項目不同曾我。通常箕昭,我們將創(chuàng)建一個有初始化方法的Webservice類來呼叫一個特定的 endpoints 栅炒。這些方法返回從 endpoints 通過一個回調(diào)函數(shù)獲得的數(shù)據(jù)秫筏。舉個例子种冬,我們可以有個網(wǎng)絡(luò)請求的loadEpisodes方法付燥,分析結(jié)果敷钾,初始化一些 Episode對象枝哄,并返回一個包含Episode的數(shù)組。我們同樣可以有一個loadMedia方法阻荒,通過同樣的步驟來夾在一個特定 episode 的 media:

final class Webservice {
    func loadEpisodes(completion: ([Episode]?) -> ()) {
        // TODO
    }

    func loadMedia(episode: Episode, completion: (Media?) -> ()) {
        // TODO
    }
}

final可以用來修飾 class挠锥,func 或者 var ,修飾過后的內(nèi)容不允許被重寫或者繼承侨赡。

0:50 在 Objective-C 中蓖租,這個方式的優(yōu)點是回調(diào)結(jié)果有個正確的類型。舉個例子羊壹,我們將獲得一個 episodes 的數(shù)組而不僅僅是個id類型蓖宦,因為這是一個從網(wǎng)絡(luò)加載任何數(shù)據(jù)的方法。這個方式的優(yōu)點是每個方法在幕后執(zhí)行一個復(fù)雜任務(wù):網(wǎng)絡(luò)請求油猫,分析數(shù)據(jù)稠茂,初始化一些 model 對象,最后通過回調(diào)返回他們情妖。這里有很多地方會出錯睬关,正因為如何,調(diào)試是很難的毡证。因為這些方法還是異步的电爹,所以讓他們更難調(diào)試。此外情竹,我們需要一個網(wǎng)絡(luò)棧設(shè)置或者模擬,這也使調(diào)試更復(fù)雜。在 Swift 中秦效,有其他的方式來讓這事簡單化雏蛮。

The Resource Struct

1:51 我們創(chuàng)建一個Resource結(jié)構(gòu)體,這是一個泛型類型阱州。這個結(jié)構(gòu)體有2個屬性:endpoint 的 URL和parse函數(shù)挑秉。parse函數(shù)試圖將一些數(shù)據(jù)轉(zhuǎn)化為結(jié)果:

struct Resource<A> {
    let url: NSURL
    let parse: NSData -> A?
}

2:12 parse函數(shù)的返回類型是可選的因為分析可能失敗。代替可選值苔货,我們也可以使用Result類型或者使他拋出詳細(xì)的錯誤信息犀概。此外,如果我們只想處理 JSON夜惭,parse函數(shù)可以使用AnyObject來代替NSData姻灶。然而,使用AnyObject會阻止我們使用我們的Resource除了 JSON - 例如圖片诈茧。

2:59 現(xiàn)在創(chuàng)建episodesResource产喉。這只是一個返回NSData的簡單 resource:

let episodesResource = Resource<NSData>(url: url, parse: { data in
    return data
})

3:33 最后,這個 resource 應(yīng)該有一個[Episode]的 result 類型敢会。我們將重構(gòu)parse函數(shù)通過幾個步驟將NSData的 result 改成[Episode]的 result 類型曾沈。

The Webservice Class

3:58 從網(wǎng)上加載資源,我們創(chuàng)建一個Webservice類鸥昏,他只有一個方法:load塞俱。這個方法是通用的,并將 resource 作為第一個參數(shù)吏垮。這二個參數(shù)是個閉包障涯,使用 A?是因為請求有可能失敗或者某些東西會出錯。在load方法里惫皱,我們使用NSURLSession.sharedSession()來做請求像樊。我們創(chuàng)建一個 data task 用從 resource 中獲得的 URL。resource 捆綁了我們需要的所有做請求的信息旅敷。目前生棍,只包含了 URL,但在將來會有更多的屬性媳谁。在 data task 的回調(diào)里涂滴,我們使用 data 作為第一個參數(shù)。我們忽略其他2個參數(shù)晴音。最后柔纵,開始 data task,我們調(diào)用resume

final class Webservice {
    func load<A>(resource: Resource<A>, completion: (A?) -> ()) {
        NSURLSession.sharedSession().dataTaskWithURL(resource.url) { data, _, _ in
            if let data = data {
                completion(resource.parse(data))
            } else {
                completion(nil)
            }
        }.resume()
    }
}

5:38 調(diào)用閉包锤躁,我們不得不通過parse函數(shù)來將 data 轉(zhuǎn)為資源的結(jié)果類型搁料。因為 data 是可選的,我們使用可選綁定。如果 data 是nil,我們調(diào)用閉包使用nil郭计。如果 data 不是nil,我們調(diào)用閉包使用parse函數(shù)霸琴。

6:22 因為我們運行在 playground,我們必須讓他一直執(zhí)行下去,否則昭伸,主線程完成就會停止:

import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

7:00 我們創(chuàng)建一個Webservice實例然后調(diào)用load方法和episodesResource一起梧乘。在閉包里,我們輸出 result:

Webservice().load(episodesResource) { result in
    print(result)
}

7: 18 在控制臺中庐杨,我們可以看到一些原始的二進(jìn)制數(shù)據(jù)选调。在我們繼續(xù)之前,我們將重構(gòu)load方法--我們不喜歡調(diào)用2次completion灵份。我們嘗試使用guard let仁堪。然而,我們還是調(diào)用了2次completion各吨,還添加了返回語句:

final class Webservice {
    func load<A>(resource: Resource<A>, completion: (A?) -> ()) {
        NSURLSession.sharedSession().dataTaskWithURL(resource.url) { data, _, _ in
            guard let data = data else {
                completion(nil)
                return
            }
            completion(resource.parse(data))
        }.resume()
    }
}

8:07 使用flatMap是其他的辦法枝笨。首先,我們嘗試map揭蜒。然而横浑,map給我們了一個A??代替A?。使用flatMap將移除2個可選:

final class Webservice {
    func load<A>(resource: Resource<A>, completion: (A?) -> ()) {
        NSURLSession.sharedSession().dataTaskWithURL(resource.url) { data, _, _ in
            let result = data.flatMap(resource.parse)
            completion(result)
        }.resume()
    }
}

flatMap可以去掉空值

Parsing JSON

8:58 下一步我們改變episodesResource為了將NSData解析為 JSON 對象屉更。我們使用內(nèi)置的 JSON 解析徙融。因為 JSON 解析會 throwing operation,我們使用try?來調(diào)用 parsing 方法:

let episodesResource = Resource<AnyObject>(url: url, parse: { data in
    let json = try? NSJSONSerialization.JSONObjectWithData(data, options: [])
    return json
})

9:40 在側(cè)邊欄瑰谜,我們可以看到二進(jìn)制數(shù)據(jù)被解析欺冀。這是個字典數(shù)組,所以我們可以讓結(jié)果類型更加明確萨脑。JSON 字典包含一個 String的 key 和AnyObject的 values:

typealias JSONDictionary = [String: AnyObject]

let episodesResource = Resource<[JSONDictionary]>(url: url, parse: { data in
    let json = try? NSJSONSerialization.JSONObjectWithData(data, options: [])
    return json as? [JSONDictionary]
})

10:23 下一步是返回一個Episode數(shù)組隐轩,所以我們需要將 JSON 字典轉(zhuǎn)化到Episode里。在初始化之前渤早,我們添加一些屬性到Episode里:idtitle职车,都是String。在真實的項目里鹊杖,這里有更多的屬性:

struct Episode {
    let id: String
    let title: String
    // ...
}

11:13 我們現(xiàn)在在 extension 里寫個可失敗構(gòu)造器悴灵。在這個 extension 里,我們保留了默認(rèn)的成員逐一初始化骂蓖。在這個構(gòu)造器里积瞒,我們首先需要檢查字典是否包含我們需要的數(shù)據(jù)。我們使用guard來做這件事登下,然后我們檢查字典里的 id是否是Srting類型茫孔,取出title做相同的操作叮喳。如果 guard 失敗,我們馬上返回nil缰贝。如果成功嘲更,我們給 idtitle賦值:

extension Episode {
    init?(dictionary: JSONDictionary) {
        guard let id = dictionary["id"] as? String,
            title = dictionary["title"] as? String else { return nil }
        self.id = id
        self.title = title
    }
}

12:48 我們現(xiàn)在重構(gòu)episodesResource來返回一個Episode數(shù)組。首先揩瞪,我們檢查我們是否有個 JSON 字典。否則篓冲,我們馬上返回nil李破。字典轉(zhuǎn)化為 episodes,我們可以使用map并使用可失敗Episode.init作為我們的轉(zhuǎn)換函數(shù)。然而壹将,構(gòu)造器返回可選值嗤攻,所以使用map結(jié)果是[Episode?]。但是我們不想在這里返回nil,應(yīng)該是[Episode]诽俯。我們使用flatMap來修復(fù)這個問題妇菱。

12:48 code

14:18 在我們的項目里,flatMap的不同版本暴区。flatMap會默認(rèn)忽略不能解析的字典闯团,我們想一旦字典無效就完全失敗:

extension SequenceType {
    public func failingFlatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]? {
        var result: [T] = []
        for element in self {
            guard let transformed = try transform(element) else { return nil }
            result.append(transformed)
        }
        return result
    }
}

14:52 我們可以重構(gòu)我們的parse函數(shù)來移除2個return仙粱。首先房交,我們嘗試使用guard,但是這個不能移除2個return伐割。然而候味,guard可以讓我們擺脫嵌套:

let episodesResource = Resource<[Episode]>(url: url, parse: { data in
    let json = try? NSJSONSerialization.JSONObjectWithData(data, options: [])
    guard let dictionaries = json as? [JSONDictionary] else { return nil }
    return dictionaries.flatMap(Episode.init)
})

15:28 我們嘗試在dictionaries里使用 optional chaining來去除2次return

let episodesResource = Resource<[Episode]>(url: url, parse: { data in
    let json = try? NSJSONSerialization.JSONObjectWithData(data, options: [])
    let dictionaries = json as? [JSONDictionary]
    return dictionaries?.flatMap(Episode.init)
})

15:44 這開始變得難以理解。我們有一個可選的dictionaries然后我們使用 optional chaining 來調(diào)用flatMap,將可失敗構(gòu)造器作為參數(shù)隔心。在這里白群,我們也許會用guard的版本,那個更加清晰硬霍。

JSON Resources

16:07 一旦我們創(chuàng)建更多的 resources帜慢,必須復(fù)制 JSON 解析到每個 resources。移除這個復(fù)制须尚,我們可以創(chuàng)建一個不同的 resources崖堤。然而,我們可以擴展現(xiàn)存的 resources 通過其他的構(gòu)造器耐床。這個構(gòu)造器頁使用 URL密幔,但是 parse 函數(shù)類型是AnyObject -> A?。我們在包裹了這個 parse 函數(shù)在其他的NSData -> A?函數(shù)類型里并在這個閉包里從episodesResource里移除了 JSON 解析撩轰。因為解析 JSON 是可選的胯甩,我們可以使用flatMap來調(diào)用parseJSON:

extension Resource {
    init(url: NSURL, parseJSON: AnyObject -> A?) {
        self.url = url
        self.parse = { data in
            let json = try? NSJSONSerialization.JSONObjectWithData(data, options: [])
            return json.flatMap(parseJSON)
        }
    }
}

18:00 現(xiàn)在我們可以使用新的構(gòu)造器來改變我們的episodesResource

let episodesResource = Resource<[Episode]>(url: url, parseJSON: { json in
    guard let dictionaries = json as? [JSONDictionary] else { return nil }
    return dictionaries.flatMap(Episode.init)
})

Naming the Resources

18:17 另外一件我們不喜歡的事情是episodesResource在公共的命名空間昧廷。我們也不喜歡他的命名。我們可以將episodesResource移到Episode的擴展里作為一個類屬性偎箫。我們將他重命名為allEpisodesResource木柬。然而,我們還是不怎么喜歡這個名字淹办∶颊恚看看這個類型,很清楚的表明它屬于Episode怜森。從類型里也可以明白是一個 resource速挑,所以我們?yōu)槭裁床粌H僅命名為call?:

18:17 code
Webservice().load(Episode.all) { result in
    print(result)
}

19:40 其實這是個危險的命名副硅,也許你會和集合混淆姥宝。雖然我們不認(rèn)為這是個問題,因為你試圖使用集合會立即失敗恐疲。

20:09 在Episode擴展中腊满,我們也可以添加其他依賴于 episode 的屬性的resources——例如,一個mediaresource培己,從指定的 episode 中獲得 media碳蛋。在media resource 中,我們可以使用字符串插入來組成 URL:

extension Episode {
    var media: Resource<Media> {
        let url = NSURL(string: "http://localhost:8000/episodes/\(id).json")!
        // TODO Return the resource ...
    }
}

21:18 如果我們在Episode結(jié)構(gòu)體中需要更多的參數(shù)是無效的省咨,我們可以改變 resource 屬性作為一個方法然后直接傳遞參數(shù)疮蹦。

21:27 我們喜歡這個網(wǎng)絡(luò)請求的方式因為幾乎所有的代碼都是同步的。這很簡單茸炒,很容易調(diào)試愕乎,而且我們也不需要設(shè)置網(wǎng)絡(luò)棧或者調(diào)試一些東西壁公。唯一異步的代碼是Webservice.load方法感论。這個架構(gòu)是個不錯的例子對于 Swift 來說;Swift 的泛型和結(jié)構(gòu)體讓這樣設(shè)計變得很簡單紊册。同樣的事情在 OC 里是做不了的比肄。

22:21 讓我們添加POST支持在下一節(jié)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末囊陡,一起剝皮案震驚了整個濱河市芳绩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撞反,老刑警劉巖妥色,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異遏片,居然都是意外死亡嘹害,警方通過查閱死者的電腦和手機撮竿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笔呀,“玉大人幢踏,你說我怎么就攤上這事⌒硎Γ” “怎么了房蝉?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長微渠。 經(jīng)常有香客問我惨驶,道長,這世上最難降的妖魔是什么敛助? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮屋确,結(jié)果婚禮上纳击,老公的妹妹穿的比我還像新娘。我一直安慰自己攻臀,他們只是感情好焕数,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著刨啸,像睡著了一般堡赔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上设联,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天善已,我揣著相機與錄音,去河邊找鬼离例。 笑死换团,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宫蛆。 我是一名探鬼主播艘包,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼耀盗!你這毒婦竟也來了想虎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叛拷,失蹤者是張志新(化名)和其女友劉穎舌厨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忿薇,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡邓线,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年淌友,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骇陈。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡震庭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出你雌,到底是詐尸還是另有隱情器联,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布婿崭,位于F島的核電站拨拓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏氓栈。R本人自食惡果不足惜渣磷,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望授瘦。 院中可真熱鬧醋界,春花似錦、人聲如沸提完。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽徒欣。三九已至逐样,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間打肝,已是汗流浹背脂新。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粗梭,地道東北人戏羽。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像楼吃,于是被迫代替她去往敵國和親始花。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理孩锡,服務(wù)發(fā)現(xiàn)酷宵,斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 泛型編碼的目的 表達(dá)算法或者數(shù)據(jù)結(jié)構(gòu)所要求的核心接口躬窜。(核心接口是什么呢浇垦?也就是找到想要實現(xiàn)的功能的最小需求。) ...
    請叫我小陳陳閱讀 1,168評論 9 8
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,233評論 0 4
  • 2017年6月13日 今天我要感恩我自己朴摊,今天一天沒有情緒心情很很好沒有一絲的雜念一天沉浸在喜悅和平的感覺里。 現(xiàn)...
    無縫鋼管皇后閱讀 196評論 0 3
  • 由于之前一直在忙項目此虑,很久沒有寫過一篇像樣的文章了甚纲,現(xiàn)在手上的項目基本是完成了,正好工作時間偷個懶寫兩篇文章朦前。 將...
    T92閱讀 2,979評論 1 4