iOS apprentice中文版 - Chapter 34:Networking

Chapter 34:Networking

現(xiàn)在準備工作已經(jīng)完成旅赢,你終于可以做些很棒的事情:在應(yīng)用程序中添加網(wǎng)絡(luò)殴玛,這樣你就可以從iTunes商店下載實際數(shù)據(jù)了!

為了讓子公司更容易找到產(chǎn)品案站,蘋果推出了一項可以查詢iTunes商店的web服務(wù)。你不會成為StoreSearch的會員嘶朱,但是你可以使用免費的web服務(wù)來執(zhí)行搜索溯捆。

在本章中配并,您將學習以下內(nèi)容:

  1. 查詢iTunes web服務(wù):web服務(wù)介紹和查詢蘋果iTunes商店web服務(wù)的詳細信息括荡。

  2. 發(fā)送HTTP請求:如何創(chuàng)建合適的URL來查詢web服務(wù),以及如何向服務(wù)器發(fā)送請求溉旋。

  3. 解析JSON:如何理解從服務(wù)器發(fā)送的JSON信息畸冲,并將其轉(zhuǎn)換為具有可在應(yīng)用程序中使用的屬性的對象。

  4. 處理JSON結(jié)果

  5. 搜索結(jié)果排序:探索不同的方法來排序搜索結(jié)果的字母观腊,以便編寫最簡潔和緊湊的代碼邑闲。


1. 查詢iTunes web服務(wù)

那么什么是web服務(wù)呢?您的應(yīng)用程序(也稱為“客戶機”)將使用HTTP協(xié)議通過網(wǎng)絡(luò)向iTunes商店(即“服務(wù)器”)發(fā)送一條消息。
由于iPhone可以連接到不同類型的網(wǎng)絡(luò)——Wi-Fi或LTE梧油、3G或GPRS等蜂窩網(wǎng)絡(luò)——該應(yīng)用程序必須“說出”各種網(wǎng)絡(luò)協(xié)議苫耸,才能與互聯(lián)網(wǎng)上的其他電腦通信。


幸運的是儡陨,你不用擔心這些褪子,因為iPhone固件會處理這個復雜的過程量淌。您只需要知道您正在使用HTTP。

HTTP是web瀏覽器在訪問web站點時使用的相同協(xié)議褐筛。事實上类少,您可以使用web瀏覽器使用iTunes web服務(wù)叙身。這是了解web服務(wù)如何工作的好方法渔扎。

這個技巧并不適用于所有的web服務(wù)—有些服務(wù)需要POST請求而不是GET請求,如果您不知道這意味著什么信轿,現(xiàn)在不要擔心—但是通常情況下晃痴,僅使用web瀏覽器就可以達到相當?shù)男Ч?br> 打開你最喜歡的網(wǎng)絡(luò)瀏覽器——我用的是Safari瀏覽器——打開下面的網(wǎng)址:
http://itunes.apple.com/search?term=metallica
瀏覽器會下載一個文件。如果您在文本編輯器中打開該文件财忽,它應(yīng)該包含如下內(nèi)容:

{
"resultCount":50,
"results": [
{"wrapperType":"track", "kind":"song", "artistId":3996865, "collectionId":579372950, "trackId":579373079, "artistName":"Metallica", "collectionName":"Metallica", "trackName":"Enter Sandman", "collectionCensoredName":"Metallica", "trackCensoredName":"Enter Sandman", "artistViewUrl":"https://itunes.apple.com/us/artist/metallica/id3996865?uo=4", "collectionViewUrl":"https://itunes.apple.com/us/album/enter-sandman/id579372950?i=579373079&uo=4", "trackViewUrl":"https://itunes.apple.com/us/album/enter-sandman/id579372950?i=579373079&uo=4", "previewUrl":"http://a38.phobos.apple.com/us/r30/Music7/v4/bd/fd/e4/bdfde4e4-5407-9bb0-e632-edbf079bed21/mzaf_907706799096684396.plus.aac.p.m4a", "artworkUrl30":"http://is1.mzstatic.com/image/thumb/Music/v4/0b/9c/d2/0b9cd2e7-6e76-8912-0357-14780cc2616a/source/30x30bb.jpg", "artworkUrl60":"http://is1.mzstatic.com/image/thumb/Music/v4/0b/9c/d2/0b9cd2e7-6e76-8912-0357-14780cc2616a/source/60x60bb.jpg", "artworkUrl100":"http://is1.mzstatic.com/image/thumb/Music/v4/0b/9c/d2/0b9cd2e7-6e76-8912-0357-14780cc2616a/source/100x100bb.jpg", "collectionPrice":9.99, "trackPrice":1.29, "releaseDate":"1991-07-29T07:00:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"notExplicit", "discCount":1, "discNumber":1, "trackCount":12, "trackNumber":1, "trackTimeMillis":331560, "country":"USA", "currency":"USD", "primaryGenreName":"Metal", "isStreamable":true},. . .

這些是iTunes網(wǎng)絡(luò)服務(wù)提供給你的搜索結(jié)果倘核。數(shù)據(jù)采用JSON格式,JSON代表JavaScript Object Notation即彪。
JSON通常用于在服務(wù)器和客戶端(即應(yīng)用程序)之間來回發(fā)送結(jié)構(gòu)化數(shù)據(jù)紧唱。您可能聽說過的另一種數(shù)據(jù)格式是XML,但它正在被JSON快速取代隶校。
您可以使用多種工具使JSON輸出對普通人更具可讀性漏益。我安裝了一個Quick Look插件,可以在一個彩色視圖中呈現(xiàn)JSON文件(www.sagtau.com/quicklookjson.html)深胳。

您確實需要先將服務(wù)器的輸出保存到一個擴展名為.json的文件中绰疤,然后按下空格鍵從Finder打開它:


注意:您可以找到Safari(以及大多數(shù)其他瀏覽器)的擴展,它們可以在瀏覽器中直接美化JSON舞终。github.com/rfletcher/safari-json-formatter就是一個很好的例子轻庆。
Mac應(yīng)用程序商店中也有專用的工具,例如Visual JSON敛劝,它允許您直接在服務(wù)器上執(zhí)行請求余爆,并以結(jié)構(gòu)化和可讀的格式顯示輸出。
一個很棒的在線工具是 codebeautify.org/jsonviewer夸盟。

瀏覽一下JSON文本蛾方。您將看到服務(wù)器返回了一系列items,其中一些是歌曲满俗,其他的是有聲讀物或音樂視頻转捕。
每個項目都有一堆與之相關(guān)的數(shù)據(jù),比如藝人的名字——“Metallica”唆垃,這是您搜索的——還有曲目名稱五芝、流派、價格辕万、發(fā)行日期等等枢步。
您將在SearchResult類中存儲這些字段沉删,以便在屏幕上顯示它們。

你從iTunes商店得到的結(jié)果可能和我的不一樣醉途。默認情況下矾瑰,搜索最多返回50個條目,由于商店中有超過50個匹配“metallica”的條目隘擎,每次搜索都可能返回50個不同的結(jié)果集殴穴。
還要注意,其中一些字段货葬,如artistViewUrl和artworkUrl100以及previewUrl是鏈接/ url采幌。繼續(xù)在瀏覽器中復制粘貼這些url,看看會發(fā)生什么震桶。

artstviewurl將打開藝術(shù)家的iTunes預(yù)覽頁面休傍,artworkUrl100加載縮略圖,預(yù)覽url將打開長達30秒的音頻預(yù)覽蹲姐。

這是服務(wù)器告訴您附加資源的方式磨取。圖像等等并沒有直接嵌入到搜索結(jié)果中,但是會給您一個URL柴墩,允許您單獨下載每個條目忙厌。試試JSON數(shù)據(jù)中的其他url,看看它們是怎么做的!

回到最初的HTTP請求拐邪。您讓web瀏覽器轉(zhuǎn)到以下URL:

http://itunes.apple.com/search?term=the+search+term

您還可以添加其他參數(shù)慰毅,使搜索更加具體。例如:

http://itunes.apple.com/search?term=metallica&entity=song

現(xiàn)在搜索結(jié)果將不包含任何音樂視頻或播客扎阶,只包含歌曲汹胃。
如果搜索項中有空格,應(yīng)該用+號替換东臀,如:

http://itunes.apple.com/search?term=pokemon+go&entity=software

搜索所有與《PokemanGo》有關(guān)的應(yīng)用程序——你可能聽說過其中一些着饥。
這個特定查詢的JSON結(jié)果中的字段與以前略有不同。沒有previewUrls惰赋,但是每個條目都有幾個screenshotUrls宰掉。不同類型的產(chǎn)品——歌曲、電影赁濒、軟件——返回不同類型的數(shù)據(jù)轨奄。

就是這樣。使用搜索參數(shù)構(gòu)造一個到itunes.apple.com的URL拒炎,然后使用該URL發(fā)出HTTP請求挪拟。服務(wù)器會發(fā)送一些JSON的官樣文章到應(yīng)用程序,你必須把它轉(zhuǎn)換成SearchResult對象击你,放到表格視圖中玉组。讓我們開始吧!

同步網(wǎng)絡(luò) = 不好

在你開始之前谎柄,我應(yīng)該指出,在你的應(yīng)用程序中惯雳,有一種不好的網(wǎng)絡(luò)方式朝巫,也有一種好的方式。

不好的方法是在應(yīng)用程序的主線程上執(zhí)行HTTP請求——這種編程方式很簡單石景,但它會阻塞用戶界面劈猿,使應(yīng)用程序在聯(lián)網(wǎng)時無法響應(yīng)。因為它會阻塞應(yīng)用程序的其余部分鸵钝,所以這被稱為同步網(wǎng)絡(luò)(Synchronous networking)糙臼。

不幸的是,許多程序員堅持用錯誤的方式在他們的應(yīng)用程序中建立網(wǎng)絡(luò)恩商,這使得應(yīng)用程序運行緩慢,容易崩潰必逆。

我將首先演示簡單但不太好的方法怠堪,只是告訴您如何不這樣做。認識到同步網(wǎng)絡(luò)的后果很重要名眉,這樣您就可以在自己的應(yīng)用程序中避免同步網(wǎng)絡(luò)粟矿。

在我讓你相信這種方法的邪惡之后,我將向你展示如何正確地做這件事——它只需要對代碼做一個小的修改损拢,但可能需要你在思考這些問題時做一個大的改變陌粹。

異步網(wǎng)絡(luò)(Asynchronous networking)——正確的類型,加上一個“a”——使您的應(yīng)用程序響應(yīng)更快福压,但也帶來了您需要額外處理的復雜性掏秩。


2.發(fā)送HTTP請求

為了查詢iTunes Store web服務(wù),您必須做的第一件事就是向iTunes服務(wù)器發(fā)送HTTP請求荆姆。這包括幾個步驟蒙幻,比如創(chuàng)建一個帶有正確搜索參數(shù)的URL,向服務(wù)器發(fā)送請求胆筒,獲得響應(yīng)等等邮破。

為請求創(chuàng)建URL

?給searchviewcontroller添加一個新方法。

// MARK:- Helper Methods
func iTunesURL(searchText: String) -> URL {
  let urlString = String(format: 
      "https://itunes.apple.com/search?term=%@", searchText)
  let url = URL(string: urlString)
  return url!
}

這首先通過將搜索文本放在“term=”參數(shù)后面來構(gòu)建URL字符串仆救,然后將該字符串轉(zhuǎn)換為URL對象抒和。
因為URL(string:)是一個可失敗的初始化器,所以它返回一個optional彤蔽。您使用url!強制打開它并返回一個實際的URL對象摧莽。

HTTPS和HTTP
先前你使用http://,但在這里你使用https://铆惑。不同之處在于HTTPS是HTTP的安全加密版本范嘱。它可以保護你的用戶不被竊聽送膳。底層協(xié)議是相同的,但是您發(fā)送或接收的任何字節(jié)在它們進入網(wǎng)絡(luò)之前都是加密的丑蛤。
從iOS 9開始叠聋,蘋果建議應(yīng)用程序應(yīng)該始終使用HTTPS。事實上受裹,即使您指定了一個不受保護的http:// URL, iOS仍然會嘗試使用HTTPS連接碌补。如果服務(wù)器沒有配置為使用HTTPS,那么網(wǎng)絡(luò)連接將會失敗棉饶。
你可以要求在你的info.plist文件中免除這種行為厦章,但一般不建議這樣做。稍后照藻,您將了解如何做到這一點袜啃,因為藝術(shù)圖像托管在不支持HTTPS的服務(wù)器上。

?將searchBarSearchButtonClicked(_:)更改為:

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  if !searchBar.text!.isEmpty {
    searchBar.resignFirstResponder()

    hasSearched = true
    searchResults = []

    let url = iTunesURL(searchText: searchBar.text!)
    print("URL: '\(url)'")

    tableView.reloadData()
  }
}

您已經(jīng)刪除了創(chuàng)建偽SearchResult項的代碼幸缕,取而代之的是調(diào)用新的iTunesURL(searchText:)方法群发。出于測試目的,您需要記錄這個方法返回的URL對象发乔。

這個邏輯位于一個if語句中熟妓,所以除非用戶實際在搜索欄中輸入文本,否則這一切都不會發(fā)生——在iTunes商店中搜索“nothing”沒有多大意義栏尚。

注意:不要被一行中的所有感嘆號搞混起愈,
如果searchBar.text ! .isEmpty
第一個是“l(fā)ogical not”操作符,因為只有當文本不為空時译仗,才希望進入if語句抬虽。第二個感嘆號用于強制展開searchBar的值。text是可選的古劲,它永遠不會是nil斥赋,所以它是可選的有點傻,但你會怎么做?

運行應(yīng)用程序产艾,輸入一些單個單詞的搜索文本疤剑,例如“metallica”,或者你最喜歡的金屬樂隊之一闷堡,然后按下搜索按鈕隘膘。
Xcode現(xiàn)在應(yīng)該在它的Debug窗格中顯示這個:

URL: 'https://itunes.apple.com/search?term=metallica'

看起來不錯。

?現(xiàn)在在搜索框中輸入一個或多個空格的搜索詞杠览,比如“pokemon go”弯菊。
哎呀,應(yīng)用程序崩潰了!

“!【 bordered width=80%】(images/34-Networking/The-crash-after-searching.png "The crash after searching for "pokemon go"")”

查看Xcode調(diào)試器的左邊窗格Variables view踱阿,您將看到url常量的值為nil——它也可能顯示為0x0000……后面跟著一串0管钳。

這個應(yīng)用程序顯然沒有創(chuàng)建一個有效的URL對象钦铁。但是為什么呢?

URL中的空格不是有效字符。許多其他字符也是無效的——比如<或>符號——因此必須轉(zhuǎn)義(escaped)才漆。另一個術(shù)語是URL編碼(URL encoding)牛曹。

例如,空格可以編碼為+符號(您在前面將URL輸入web瀏覽器時就這樣做了)醇滥,或者字符序列%20黎比。

幸運的是,String已經(jīng)可以進行這種編碼了鸳玩。所以阅虫,你只需要在應(yīng)用程序中添加一個額外的語句就可以實現(xiàn):

func iTunesURL(searchText: String) -> URL {
  let encodedText = searchText.addingPercentEncoding(
      withAllowedCharacters: CharacterSet.urlQueryAllowed)!
  let urlString = String(format: 
      "https://itunes.apple.com/search?term=%@", encodedText)
  let url = URL(string: urlString)
  return url!
}

這將調(diào)用addingPercentEncoding(withAllowedCharacters:)方法來創(chuàng)建一個新字符串,其中所有特殊字符都轉(zhuǎn)義不跟,您將該字符串用于搜索項颓帝。

utf - 8編碼字符串

這個新字符串將特殊字符視為“UTF-8編碼”。知道這是什么意思很重要躬拢,因為在處理文本時躲履,偶爾會遇到這個UTF-8。
編碼文本有許多不同的方法聊闯。您可能聽說過ASCII和Unicode,這是兩種最常見的編碼米诉。
UTF-8是Unicode的一個版本菱蔬,它對于存儲常規(guī)文本非常有效,但是對于特殊符號或非西方字母就不那么有效了史侣。盡管如此拴泌,它仍然是當今處理Unicode文本最流行的方法。
通常惊橱,您不必擔心字符串是如何編碼的蚪腐。但是,當向web服務(wù)發(fā)送請求時税朴,需要使用正確的編碼傳輸文本回季。提示:當有疑問時,使用UTF-8正林,它幾乎總是有效的泡一。

?運行應(yīng)用程序,再次搜索“pokemon go”觅廓。這一次可以創(chuàng)建一個有效的URL對象鼻忠,它看起來像這樣:

URL: 'https://itunes.apple.com/search?term=pokemon%20go'

空格已轉(zhuǎn)換為字符序列%20。%表示轉(zhuǎn)義字符杈绸,20是空格的UTF-8值帖蔓。你也可以試著用其他特殊字符搜索詞條矮瘟,比如#和*,甚至是表情符號塑娇,看看會發(fā)生什么澈侠。

執(zhí)行搜索請求

現(xiàn)在您有了一個有效的URL對象,您就可以進行一些實際的網(wǎng)絡(luò)連接了!
?給SearchViewController.swift添加一個新方法钝吮。

func performStoreRequest(with url: URL) -> String? {
  do {
   return try String(contentsOf: url, encoding: .utf8)
  } catch {
   print("Download Error: \(error.localizedDescription)")
   return nil
  }
}

這個方法的核心是對String(contentsOf:encoding:)的調(diào)用埋涧,它返回一個新的String對象,其中包含它從URL指向的服務(wù)器接收到的數(shù)據(jù)奇瘦。

注意棘催,您正在告訴應(yīng)用程序?qū)?shù)據(jù)解釋為UTF-8文本。如果服務(wù)器以不同的編碼發(fā)送回文本耳标,那么在你的應(yīng)用程序中醇坝,它看起來就像一團亂麻。重要的是發(fā)送方和接收方同意他們使用的編碼!

因為事情可能出錯——例如次坡,網(wǎng)絡(luò)可能宕機串述,無法訪問服務(wù)器——所以您將其封裝在do-try-catch塊中。如果出現(xiàn)問題谣辞,代碼將跳轉(zhuǎn)到catch分支变过,錯誤變量將包含關(guān)于錯誤的更多細節(jié)。如果發(fā)生這種情況症脂,您將打印出用戶可理解的錯誤形式谚赎,并返回nil,以表明請求失敗诱篷。

?在print()行之后壶唤,將以下幾行添加到searchBarSearchButtonClicked(_:)中:

if let jsonString = performStoreRequest(with: url) {
  print("Received JSON string '\(jsonString)'")
}

這將使用URL對象作為參數(shù)調(diào)用performStoreRequest(with:),并返回從服務(wù)器接收到的JSON數(shù)據(jù)棕所。如果一切正常闸盔,從服務(wù)器接收。如果一切都按計劃進行琳省,這個方法將返回一個包含您正在尋找的JSON數(shù)據(jù)的新字符串迎吵。我們來試試吧!

運行應(yīng)用程序并搜索你最喜歡的樂隊。大約一秒鐘后岛啸,一大堆數(shù)據(jù)將被轉(zhuǎn)儲到Xcode控制臺:

URL: 'http://itunes.apple.com/search?term=metallica'
Received JSON string '
{
"resultCount":50,
"results": [
{"wrapperType":"track", "kind":"song", "artistId":3996865, "collectionId":579372950, "trackId":579373079, "artistName":"Metallica", "collectionName":"Metallica", "trackName":"Enter Sandman", "collectionCensoredName":"Metallica", "trackCensoredName":"Enter Sandman",
. . . and so on . . .

祝賀您钓觉,您的應(yīng)用程序已經(jīng)成功地與web服務(wù)進行了對話!
這將打印與您之前在web瀏覽器中看到的相同的內(nèi)容。現(xiàn)在它全部包含在一個String對象中坚踩,這對于我們的目的來說并不是很有用荡灾,但是您很快就會將它轉(zhuǎn)換成更有用的格式。
當然,您可能收到了錯誤批幌。在這種情況下础锐,輸出應(yīng)該是這樣的:

URL: 'https://itunes.apple.com/search?term=Metallica'
HTTP load failed (error code: -1009 [1:50]) for Task <F5199AB7-5011-42FB-91B5-656244861482>.<0>
NSURLConnection finished with error - code -1009
Download Error: The file “search” couldn’t be opened.

稍后,你會在應(yīng)用程序中添加更好的錯誤處理荧缘,但如果此時出現(xiàn)這樣的錯誤皆警,那么要確保你的電腦——或者你的iPhone(以防你在設(shè)備上而不是模擬器上運行應(yīng)用程序)連接到互聯(lián)網(wǎng)。還可以直接在瀏覽器中嘗試URL截粗,看看是否可行信姓。


3.解析JSON

現(xiàn)在您已經(jīng)成功地從服務(wù)器下載了一大塊JSON數(shù)據(jù),那么如何處理它呢?

JSON是一種結(jié)構(gòu)化數(shù)據(jù)格式绸罗。它通常由數(shù)組和字典組成意推,其中包含其他數(shù)組和字典,以及字符串和數(shù)字等常規(guī)數(shù)據(jù)珊蟀。

JSON數(shù)據(jù)概覽

iTunes商店中的JSON大致是這樣的:

{
"resultCount": 50,
"results": [ . . . a bunch of other stuff . . . ]
}

{}括號包圍著字典菊值。這個特定的字典有兩個鍵:resultCount和results。第一個是resultCount育灸,它有一個數(shù)值腻窒。這是匹配搜索查詢的項數(shù)。默認情況下磅崭,上限為50項儿子,但正如您稍后將看到的,您可以增加這個上限砸喻。

result鍵包含一個數(shù)組典徊,它由[]括號表示。在這個數(shù)組中有更多的字典恩够,每個字典描述商店中的一個產(chǎn)品。你可以看出這些東西是字典羡铲,因為它們又有{}括號蜂桶。

以下是數(shù)組中的兩項:

{
"wrapperType": "track",
"kind": "song",
"artistId": 3996865,
"artistName": "Metallica",
"trackName": "Enter Sandman",
. . . and so on . . .
},
{
"wrapperType": "track",
"kind": "song",
"artistId": 3996865,
"artistName": "Metallica",
"trackName": "Nothing Else Matters",
. . . and so on . . .
}

每個產(chǎn)品都由幾個鍵組成的字典表示。kind和wrapperType鍵的值決定了這是什么類型的產(chǎn)品:歌曲也切、音樂視頻扑媚、有聲讀物等等。其他的鍵描述了藝術(shù)家和歌曲本身雷恃。



簡而言之疆股,JSON數(shù)據(jù)表示一個字典,字典中包含更多的字典數(shù)組倒槐。數(shù)組中的每個字典表示一個搜索結(jié)果旬痹。
目前,所有這些都位于一個字符串中,這不是很方便两残,但是使用JSON解析器可以將這些數(shù)據(jù)轉(zhuǎn)換為Swift字典和數(shù)組對象永毅。

JSON還是XML?

JSON不是唯一的結(jié)構(gòu)化數(shù)據(jù)格式。代表可擴展標記語言的XML是一種稍微正式一些的標準人弓。這兩種格式都有相同的目的沼死,但是它們看起來有點不同。如果iTunes商店以XML格式返回結(jié)果崔赌,輸出將更像這樣:

<?xml version="1.0" encoding="utf-8"?>
<iTunesSearch>
<resultCount>5</resultCount>
<results>
<song>
<artistName>Metallica</artistName>
<trackName>Enter Sandman</trackName>
</song>
<song>
<artistName>Metallica</artistName>
<trackName>Nothing Else Matters</trackName>
</song>
. . . and so on . . .
</results>
</iTunesSearch>

如今意蛀,大多數(shù)開發(fā)人員更喜歡JSON,因為它比XML更簡單健芭,更容易解析县钥。但是,如果您希望您的應(yīng)用程序與特定的web服務(wù)對話吟榴,那么您當然有可能需要處理XML數(shù)據(jù)魁蒜。

準備解析JSON數(shù)據(jù)

過去,如果想解析JSON吩翻,通常需要在應(yīng)用程序中包含第三方框架兜看,或者使用內(nèi)置的iOS JSON解析器手動遍歷數(shù)據(jù)結(jié)構(gòu)。但有了Swift 4狭瞎,有了一種新的做事方式——你的老朋友是Codable细移。

為了讓您的應(yīng)用程序能夠直接將JSON數(shù)據(jù)讀入相關(guān)的數(shù)據(jù)結(jié)構(gòu),您所需要做的就是將它們設(shè)置為符合Codable!

“等一下”熊锭,我聽到你說弧轧。“Codable如何知道如何從互聯(lián)網(wǎng)上建立任意的數(shù)據(jù)結(jié)構(gòu)碗殷,以便正確地提取正確的數(shù)據(jù)位?”啊精绎,這完全取決于如何設(shè)置數(shù)據(jù)結(jié)構(gòu)。當你繼續(xù)解析從iTunes服務(wù)器接收到的數(shù)據(jù)時锌妻,你就會明白了代乃。

用Codable解析JSON數(shù)據(jù)的訣竅是設(shè)置類或結(jié)構(gòu)來反映將要解析的數(shù)據(jù)的結(jié)構(gòu)。正如您在上面所注意到的仿粹,從iTunes服務(wù)器接收到的JSON響應(yīng)有兩部分:

  1. 響應(yīng)包裝器(wrapper)搁吓,其中包含結(jié)果的數(shù)量和結(jié)果數(shù)組。
  2. 數(shù)組本身由單獨的搜索結(jié)果項組成吭历。

為了正確解析JSON數(shù)據(jù)堕仔,我們需要對上述兩種方法進行建模。在通過SearchResult對象對搜索結(jié)果進行建模方面晌区,我們已經(jīng)取得了一些進展摩骨,但是我們需要進行一些修改通贞,以便讓對象為JSON解析做好準備。
但首先仿吞,讓我們?yōu)榻Y(jié)果包裝器添加一個新的數(shù)據(jù)模型滑频。

?打開SearchResult.swift ,并將其內(nèi)容替換為:

class ResultArray:Codable {
    var resultCount = 0
    var results = [SearchResult]()
}

class SearchResult:Codable {
  var artistName: String? = ""
  var trackName: String? = ""
  
  var name:String {
    return trackName ?? ""
  }
}

這里有一些變化:

  1. ResultArray類通過包含一個resultCount計數(shù)和一個SearchResult對象數(shù)組來對響應(yīng)包裝器建模唤冈。注意峡迷,該類支持Codable協(xié)議。

    如果您想知道為什么這個類與SearchResult在同一個文件中你虹,這只是為了方便起見绘搞。除了在JSON解析過程中作為臨時持有者之外,其他任何地方都不使用該類傅物。所以我把它放在與SearchResult相同的文件中夯辖,這是您將使用的實際類。但如果你愿意董饰,你可以把這個類單獨放在一個Swift文件中——這對應(yīng)用的功能沒有任何影響蒿褂。

  2. SearchResult類現(xiàn)在也支持Codable協(xié)議。

  3. 它還有一個名為trackName的新屬性卒暂,而artistName屬性已被更改為可選屬性——可選屬性是為了使Codable的工作更容易啄栓,因為Codable期望非可選值總是出現(xiàn)在JSON數(shù)據(jù)中。不幸的是也祠,由于iTunes服務(wù)器的響應(yīng)可能并不總是具有這些屬性昙楚,所以您必須考慮到這一點。

  4. name的現(xiàn)有屬性已轉(zhuǎn)換為計算屬性诈嘿,該屬性返回trackName屬性的值堪旧,如果trackName為nil,則返回空字符串奖亚。

3和4改變的原因可能不是很明顯淳梦。查看從服務(wù)器接收到的響應(yīng)數(shù)據(jù)。你注意到那把“親切”的key了嗎?

iTunes的搜索結(jié)果可以是多種類型的商品——歌曲昔字、視頻谭跨、電影、電視節(jié)目李滴、書籍等等。該key指示搜索結(jié)果要查找的項的類型蛮瞄。根據(jù)項目類型所坯,您可能希望改變顯示項目名稱的方式。例如挂捅,您可能不總是希望使用“trackName”鍵作為項名——事實上芹助,正如我們上面提到的,“trackName”甚至可能不存在于返回的數(shù)據(jù)中。computed name屬性只是為將來做準備状土,以防你想要根據(jù)結(jié)果類型來顯示不同的名稱无蜂。

另外,請注意蒙谓,現(xiàn)在類中的所有屬性名都匹配JSON數(shù)據(jù)中的實際鍵——即使屬性名與鍵名不匹配斥季,也可以解析JSON,但這有點復雜累驮。讓我們走簡單的路酣倾。記住,小步……
這就是準備JSON解析所需要的全部內(nèi)容谤专。開始!

解析JSON數(shù)據(jù)

您將使用JSONDecoder類解析JSON數(shù)據(jù)躁锡。唯一的問題是,JSONDecoder需要一個Data對象作為它的輸入置侍。目前映之,服務(wù)器的JSON響應(yīng)是一個字符串String。

你可以很容易地將字符串String轉(zhuǎn)換成數(shù)據(jù)Data蜡坊,但最好首先從服務(wù)器獲取數(shù)據(jù)作為響應(yīng)——你從服務(wù)器獲取的響應(yīng)最初是字符串杠输,只是為了確保響應(yīng)是正確的。

?切換到SearchViewController.swift并修改performStoreRequest(with:)如下:

func performStoreRequest(with url: URL) -> Data? {  // Change to Data?
  do {
    return try Data(contentsOf:url)   // Change this line
  } catch {
    . . .
  }
}

您只需更改request方法算色,以從服務(wù)器獲取響應(yīng)作為數(shù)據(jù)而不是字符串—該方法現(xiàn)在返回值作為可選數(shù)據(jù)值而不是可選字符串值抬伺。

?將以下方法添加到SearchViewController.swift:

func parse(data: Data) -> [SearchResult] {
  do {
    let decoder = JSONDecoder()
    let result = try decoder.decode(ResultArray.self, from:data)
    return result.results
  } catch {
    print("JSON Error: \(error)")
    return []
  }
}

您使用JSONDecoder對象將響應(yīng)數(shù)據(jù)從服務(wù)器轉(zhuǎn)換為臨時ResultArray對象,并返回其中的results屬性灾梦。至少峡钓,您希望能夠毫無問題地轉(zhuǎn)換數(shù)據(jù)……

假設(shè)帶來麻煩

當你編寫與互聯(lián)網(wǎng)上其他電腦對話的應(yīng)用程序時,需要記住的一點是若河,你的對話伙伴可能并不總是說你希望他們說的話能岩。

服務(wù)器上可能有一個錯誤,它可能返回一些錯誤消息萧福,而不是有效的JSON數(shù)據(jù)拉鹃。在這種情況下,JSONDecoder將無法解析數(shù)據(jù)鲫忍,應(yīng)用程序?qū)膒arse返回一個空數(shù)組(data:)膏燕。

另一種可能發(fā)生的情況是,服務(wù)器的所有者改變了他們發(fā)回數(shù)據(jù)的格式悟民。通常坝辫,這是在可以通過不同URL訪問的web服務(wù)的新版本中完成的∩淇鳎或者近忙,它們可能要求您發(fā)送一個“version”參數(shù)竭业。但并不是每個人都這么小心,而且通過改變服務(wù)器的操作及舍,他們可能會破壞依賴于以特定格式返回的數(shù)據(jù)的應(yīng)用程序未辆。

在iTunes store web服務(wù)的情況下,頂層對象應(yīng)該是一個具有兩個鍵的字典——一個鍵用于計數(shù)锯玛,另一個鍵用于結(jié)果數(shù)組——但是您不能控制服務(wù)器上發(fā)生了什么咐柜。如果出于某種原因,服務(wù)器程序員決定在JSON數(shù)據(jù)周圍加上[]括號更振,那么頂級對象將不再是字典炕桨,而是數(shù)組。這將導致JSONDecoder無法解析數(shù)據(jù)肯腕,因為它不再是預(yù)期的格式献宫。

對這類事情保持偏執(zhí),顯示在不太可能發(fā)生情況下的錯誤消息实撒,可比在服務(wù)器上發(fā)生超出您控制的更改時您的應(yīng)用程序突然崩潰要好得多姊途。

可以肯定的是,您正在使用do-try-catch塊來檢查JSON解析是否順利進行知态。如果轉(zhuǎn)換失敗捷兰,那么應(yīng)用程序不會突然起火,而只是返回一個空的結(jié)果數(shù)組负敏。

在應(yīng)用程序中添加這樣的檢查是很好的贡茅,以確保您得到了預(yù)期的結(jié)果。如果您沒有與之通信的服務(wù)器其做,那么最好進行防御性編程顶考。

?將searchBarSearchButtonClicked(_:)修改如下:

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  if !searchBar.text!.isEmpty {
    . . .
    print("URL: '\(url)'")
    if let data = performStoreRequest(with: url) {  // Modified
      let results = parse(data: data)               // New line
      print("Got results: \(results)")              // New line
    }
    tableView.reloadData()
  }
}

您只需將結(jié)果的常量改為performStoreRequest(with:),從jsonString轉(zhuǎn)為data妖泄,調(diào)用新的parse(data:)方法驹沿,并print返回的值。

運行應(yīng)用程序并搜索一些內(nèi)容蹈胡。Xcode控制臺現(xiàn)在打印以下內(nèi)容:

URL: 'https://itunes.apple.com/search?term=Metallica'
Got results: [StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult, StoreSearch.SearchResult,
. . . ]

嗯……這看起來確實像一個由50個條目組成的數(shù)組渊季,但它并不能真正告訴你關(guān)于實際數(shù)據(jù)的任何信息——只是這個數(shù)組由SearchResult對象組成。那對你沒多大好處罚渐,是嗎?

打印對象的內(nèi)容

?修改SearchResult中的SearchResult類却汉,使之符合CustomStringConvertible協(xié)議:

class SearchResult: Codable, CustomStringConvertible {

CustomStringConvertible協(xié)議允許對象具有自定義字符串表示『刹ⅲ或者病涨,換句話說,協(xié)議允許對象具有描述對象或其內(nèi)容的自定義字符串璧坟。

那么既穆,協(xié)議如何提供這個字符串描述呢?這是通過協(xié)議的description屬性完成的。

?將以下代碼添加到SearchResult類中:

var description: String {
  return "Name: \(name), Artist Name: \(artistName ?? "None")"
}

上面是description屬性的實現(xiàn)雀鹃,以符合CustomStringConvertible幻工。對于SearchResult類,描述由name和artistName屬性的值組成——但是由于artistName是一個可選值黎茎,所以必須考慮什么時候它可能是nil并輸出“None”囊颅。

注意到上面代碼中的操作符? ?——它被稱為nil-coalescing操作符,您可能還記得前面幾章中的內(nèi)容傅瞻。nil-coalescing操作符將變量展開為操作符的左側(cè)的值(如果它有一個值)踢代,否則它將返回操作符右側(cè)的值作為默認值。

再次運行應(yīng)用程序并搜索一些內(nèi)容嗅骄。Xcode控制臺現(xiàn)在應(yīng)該打印如下內(nèi)容:

URL: 'https://itunes.apple.com/search?term=Metallica'
Got results: [Name: Enter Sandman, Artist Name: Metallica, Name: Nothing Else Matters, Artist Name: Metallica, Name: The Unforgiven, Artist Name: Metallica, Name: One, Artist Name: Metallica, Name: Wherever I May Roam, Artist Name: Metallica,
. . .

是的胳挎,看起來更像!

你把一堆毫無意義的JSON轉(zhuǎn)換成了你可以使用的實際對象。

錯誤處理

讓我們添加一個警告來處理潛在的錯誤溺森。這是不可避免的慕爬,有些地方出了問題,最好做好準備屏积。

?將以下方法添加到SearchViewController.swift:

func showNetworkError() {
  let alert = UIAlertController(title: "Whoops...",
    message: "There was an error accessing the iTunes Store." + 
    " Please try again.", preferredStyle: .alert)
  
  let action = UIAlertAction(title: "OK", style: .default, 
                           handler: nil)
  alert.addAction(action)
  present(alert, animated: true, completion: nil)
}

沒有什么是你之前沒見過的;它只是提供一個帶有錯誤消息的警報控制器医窿。

注意:message變量被分割成兩個單獨的字符串,并使用加號(+)操作符連接或添加在一起炊林,以便在本書中很好地顯示字符串姥卢。你可以把整個字符串作為一個單獨的字符串輸入。

在返回nil之前渣聚,將下面這行添加到performStoreRequest(with:)中:

showNetworkError()

簡單地說独榴,如果iTunes商店的請求出了問題,您可以調(diào)用showNetworkError()來顯示一個警告框饵逐。

如果到目前為止您所做的一切都是正確的括眠,那么web服務(wù)應(yīng)該始終能夠正常工作。不過倍权,測試一些錯誤情況仍然是一個好主意掷豺,只是為了確保錯誤處理對那些網(wǎng)絡(luò)連接不好的倒霉用戶有效。

試試這個:在iTunesURL(searchText:)方法中薄声,將URL的“itunes.apple.com”部分臨時更改為“NOMOREitunes.apple.com”当船。

現(xiàn)在,當您嘗試搜索時默辨,應(yīng)該會得到一個錯誤警告德频,因為該地址不存在這樣的服務(wù)器。這模擬iTunes服務(wù)器正在關(guān)閉缩幸。當您完成測試時壹置,不要忘記更改URL竞思。

提示:要模擬沒有網(wǎng)絡(luò)連接,你可以拔掉Mac電腦上的網(wǎng)絡(luò)電纜和/或禁用Wi-Fi钞护,或者在飛機模式下在設(shè)備上運行應(yīng)用程序盖喷。


4.處理JSON結(jié)果

到目前為止,您已經(jīng)成功地向iTunes web服務(wù)發(fā)送了一個請求难咕,并將JSON數(shù)據(jù)解析為SearchResult對象數(shù)組课梳。然而,我們還沒有完全完成余佃。

iTunes商店出售不同種類的產(chǎn)品——歌曲暮刃、電子書、軟件爆土、電影等等——每一種產(chǎn)品在JSON數(shù)據(jù)中都有自己的結(jié)構(gòu)椭懊。軟件產(chǎn)品會有截圖,但電影會有視頻預(yù)覽雾消。應(yīng)用程序?qū)⒉坏貌惶幚磉@些不同類型的數(shù)據(jù)灾搏。

你不會支持iTunes商店提供的所有東西,只支持這些東西:

  • 歌曲立润,音樂視頻狂窑,電影,電視節(jié)目桑腮,播客
  • 有聲書
  • 軟件(應(yīng)用程序)
  • 電子書

我把它們分成這樣是因為iTunes商店就是這么做的泉哈。例如,歌曲和音樂視頻共享同一組字段破讨,但是有聲讀物和軟件具有不同的數(shù)據(jù)結(jié)構(gòu)丛晦。JSON數(shù)據(jù)使用kind字段進行了這種區(qū)分。

讓我們修改數(shù)據(jù)模型來加載上面鍵的值提陶。

?將以下屬性添加到SearchResult (SearchResult.swift):

var kind: String? = ""

你可能認為“kind”屬性總是在iTunes數(shù)據(jù)中烫沙,所以它不需要是可選的。我也這么認為隙笆,但不幸的是锌蓄,iTunes證明我錯了:]所以我們在那里添加了一個可選值……

?還將 description的return行修改為:

return "Kind: \(kind ?? "None"), Name: \(name), Artist Name: \(artistName ?? "None")\n

這是有道理的,因為這是可選的撑柔,對吧?

運行應(yīng)用程序并進行搜索瘸爽。看看Xcode的輸出铅忿。

當我這樣做的時候剪决,Xcode展示了三種不同類型的產(chǎn)品,大多數(shù)結(jié)果是歌曲。你看到的可能會有所不同柑潦,這取決于你搜索的內(nèi)容享言。

URL: 'https://itunes.apple.com/search?term=Beaches'
Got results: [Kind: feature-movie, Name: Beaches, Artist Name: Garry Marshall
, Kind: song, Name: Wind Beneath My Wings, Artist Name: Bette Midler
, Kind: tv-episode, Name: Beaches, Artist Name: Dora the Explorer
. . .

現(xiàn)在,讓我們向SearchResult對象添加一些新屬性渗鬼。

總是檢查文檔

如果你好奇我是如何懂得從iTunes web服務(wù)解釋數(shù)據(jù),甚至如何設(shè)置urls使用該服務(wù)的,那么您應(yīng)該意識到如果沒有文檔担锤,你沒有辦法使用一個web服務(wù)。

幸運的是乍钻,對于iTunes store web服務(wù),這里有一些很好的文檔:

affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api

但是僅僅閱讀文檔通常是不夠的铭腕。你必須稍微擺弄一下web服務(wù)银择,才能知道你能做什么和不能做什么。

對于閱讀文檔時不清楚的搜索結(jié)果累舷,StoreSearch應(yīng)用程序需要做一些事情浩考。所以,首先閱讀文檔被盈,然后使用它析孽。這適用于任何API,不管是iOS SDK還是web服務(wù)只怎。

加載更多的屬性

正如你所看到的袜瞬,當前的SearchResult類只有幾個屬性,iTunes商店返回的信息比這多得多身堡,所以你需要添加一些新屬性邓尤。

?將以下屬性添加到SearchResult.swift中。

var trackPrice: Double? = 0.0
var currency = ""
var artworkUrl60 = ""
var artworkUrl100 = ""
var trackViewUrl: String? = ""
var primaryGenreName = "”

你沒有包括iTunes商店返回的所有內(nèi)容贴谎,只包括這個應(yīng)用程序感興趣的字段汞扎。此外,請注意擅这,你已經(jīng)為屬性命名澈魄,以精確匹配JSON數(shù)據(jù)中的鍵,只有一些屬性被標記為可選仲翎。

注:這些屬性的可選性是基于我自己的結(jié)果痹扇。有可能,使用上面的代碼谭确,你仍然會發(fā)現(xiàn)應(yīng)用程序到處出現(xiàn)這樣的錯誤:

URL: 'https://itunes.apple.com/search?term=Macky'
JSON Error: keyNotFound(CodingKeys(stringValue: "trackViewUrl", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "results", intValue: nil), _JSONKey(stringValue: "Index 1", intValue: 1)], debugDescription: "No value associated with key CodingKeys(stringValue: "trackViewUrl", intValue: nil) ("trackViewUrl").", underlyingError: nil))

如果這種情況發(fā)生在您身上谬莹,請查看錯誤消息,找出SearchResult中缺失的屬性拂檩,然后將其標記為optional—問題解決了!

SearchResult存儲商品的價格和貨幣——美元便贵、歐元、英鎊等等昂秃。它還存儲了兩個藝術(shù)品的urls禀梳,一個用于60×60像素的圖像杜窄,另一個用于100×100像素的圖像,一個指向iTunes商店中該產(chǎn)品頁面的鏈接算途,以及該產(chǎn)品的類型塞耕。

只要類支持Codable,只需簡單地添加新屬性——只要它們的名稱與JSON鍵相同嘴瓤,并且具有正確的可選性——你現(xiàn)在就可以將這些新值加載到你的類中扫外。但是,如果您不想使用JSON數(shù)據(jù)中不太友好的名稱廓脆,如artworkUrl60或artworkUrl100筛谚,而是希望使用更具描述性的名稱,如artworkSmall和artworkLarge停忿,該怎么辦?

不用擔心驾讲,Codable也支持這一點:]

但在此之前,你應(yīng)該運行你的應(yīng)用程序一次席赂,以確保上面的代碼更改不會破壞任何東西吮铭。因此,運行您的應(yīng)用程序颅停,進行搜索谓晌,并驗證您仍然在Xcode控制臺中得到輸出,表明搜索成功便监。

所有工作正常嗎?太棒了!讓我們繼續(xù)將SearchResults屬性命名為您想要的名稱扎谎,而不是JSON數(shù)據(jù)集的名稱……

支持更好的屬性名稱

?替換 SearchResult.swift中的以下代碼行:

var artworkUrl60 = ""
var artworkUrl100 = ""
var trackViewUrl: String? = ""
var primaryGenreName = "”

用這個:

var imageSmall = ""
var imageLarge = ""
var storeURL: String? = ""
var genre = ""

enum CodingKeys: String, CodingKey {
  case imageSmall = "artworkUrl60"
  case imageLarge = "artworkUrl100"
  case storeURL = "trackViewUrl"
  case genre = "primaryGenreName"
  case kind, artistName, trackName 
  case trackPrice, currency
}

您將注意到,您已經(jīng)將屬性名稱更改得更具描述性烧董,但是enum是做什么的呢?

正如您前面所看到的毁靶,枚舉(enum或enumeration)是一種方法,用于獲得一系列值并為這些值命名逊移。這里预吆,您使用CodingKeys枚舉讓Codable協(xié)議知道您希望如何讓SearchResult屬性匹配JSON數(shù)據(jù)。

請注意,如果你使用CodingKeys枚舉,它必須為class里所有的屬性提供一個case——那些映射到一個JSON鍵具有相同名稱的是enum最后的兩個case,你會發(fā)現(xiàn)他們沒有一個指定的值胳泉。

再次運行你的應(yīng)用程序(可能會改變description屬性拐叉,返回其中一個新值,以測試它們是否正確顯示)扇商,并驗證代碼仍然可以使用這些新屬性凤瘦。

使用結(jié)果

使用這些最新的更改,searchBarSearchButtonClicked(_:)檢索一個包含有用信息的SearchResult對象數(shù)組案铺,但是您還沒有對該數(shù)組做任何操作蔬芥。

?切換到SearchViewController.swift并在searchBarSearchButtonClicked(_:)中,替換以下行:

let results = parse(data: data)
print("Got results: \(results)"))

更改為:

searchResults = parse(data: data)

不需要將結(jié)果放在本地變量中并打印出來,而是將返回的數(shù)組放入searchResults實例變量中笔诵,這樣表視圖就可以顯示實際的搜索結(jié)果對象返吻。

運行應(yīng)用程序,搜索你最喜歡的音樂人乎婿。過了一秒鐘左右测僵,您應(yīng)該會看到表中出現(xiàn)了一大堆結(jié)果⌒霍幔酷!

不同的數(shù)據(jù)結(jié)構(gòu)

還記得我說過的一些東西捍靠,比如有聲讀物,有不同的數(shù)據(jù)結(jié)構(gòu)嗎?讓我們更詳細地討論一下……

目前森逮,其他項目類型和有聲讀物之間最大的區(qū)別是剂公,有聲讀物沒有為其他項目提供特定的JSON鍵。這里有一個故障:

  1. kind:這個值根本不存在吊宋。
  2. trackName:不是“trackName”,而是“collectionName”颜武。
  3. trackviewUrl:不是這個值璃搜,而是“collectionViewUrl”——它提供了到項目的iTunes鏈接。
  4. trackPrice:不是“trackPrice”鳞上,而是“collectionPrice”这吻。

有趣的是,你會注意到在SearchResult中這些都是我們標記為可選的屬性「菀椋現(xiàn)在你明白為什么我們要把它們標記為可選的了吧?如果你的搜索結(jié)果中包含一個有聲書條目唾糯,那么這些屬性就不會出現(xiàn)在那里,因此Codable會進行匹配:]

此外鬼贱,對于一些項目類型移怯,JSON還有一些其他的區(qū)別:

  1. 軟件和電子書沒有“trackPrice”鍵,而是有一個“price”鍵这难。
  2. 電子書沒有“primaryGenreName”鍵——它們有一系列的類型舟误。

那么,如何修復這些問題姻乓,使JSONDecoder能夠正確地解碼來自iTunes Store服務(wù)器的JSON數(shù)據(jù)嵌溢,而不管數(shù)據(jù)的類型是什么呢?您如何處理相同的屬性(例如,“trackPrice”)可以呈現(xiàn)為不同的屬性(如“collectionPrice”或“price”)的情況?這取決于項目的類型嗎蹋岩?

還記得如何添加一個名為name的計算變量來返回trackName嗎?這就是發(fā)揮作用的地方……如果您添加另一個變量來存儲collectionName(當它是一個有聲讀物時赖草,它是條目的名稱),那么您可以根據(jù)情況從name返回正確的值剪个。你也可以對商店的網(wǎng)址和價格做類似的事情秧骑。

讓我們做出必要的改變。

?從SearchResult中移除storeURL屬性——你將為有聲書和非有聲書類型添加兩個單獨的可選屬性。還要從CodingKeys中刪除storeURL案例腿堤。

?從SearchResult中移除genre屬性——你將為電子書和非電子書類型添加兩個單獨的可選屬性阀坏。還可以從CodingKeys中刪除類型案例。

?為上述特殊項目中出現(xiàn)的不同鍵添加新的可選屬性:

var trackViewUrl: String?
var collectionName: String?
var collectionViewUrl: String?
var collectionPrice: Double?
var itemPrice: Double?
var itemGenre: String?
var bookGenre: [String]?

?將name的計算屬性(computed property)替換為:

var name: String {
  return trackName ?? collectionName ?? ""
}

除了nil-coalescing操作符的鏈接之外笆檀,更改非常簡單忌堂。檢查trackName是否為nil——如果不是,則返回trackName的未包裝值酗洒。如果trackName為nil士修,則繼續(xù)到collectionName并執(zhí)行相同的檢查。如果兩個值都為nil樱衷,則返回一個空字符串棋嘲。

?添加以下三個新的計算屬性:

var storeURL: String {
  return trackViewUrl ?? collectionViewUrl ?? ""
}

var price: Double {
  return trackPrice ?? collectionPrice ?? itemPrice ?? 0.0
}

var genre: String {
  if let genre = itemGenre {
    return genre
  } else if let genres = bookGenre {
    return genres.joined(separator: ", ")
  }
  return ""
}

前兩個計算屬性的工作方式類似于name計算屬性的工作方式。這沒什么新鮮的矩桂。genre屬性只返回非電子書項的類型沸移。對于電子書,該方法將數(shù)組中由逗號分隔的所有類型值組合起來侄榴,然后返回組合后的字符串雹锣。

剩下的就是將所有的新屬性添加到CodingKeys枚舉中——如果不這樣做,在JSON解碼期間可能無法正確填充一些值癞蚕。完成之后蕊爵,CodingKeys應(yīng)該是這樣的:

enum CodingKeys: String, CodingKey {
  case imageSmall = "artworkUrl60"
  case imageLarge = "artworkUrl100"
  case itemGenre = "primaryGenreName"
  case bookGenre = "genres"
  case itemPrice = "price"
  case kind, artistName, currency
  case trackName, trackPrice, trackViewUrl
  case collectionName, collectionViewUrl, collectionPrice
}

再次運行應(yīng)用程序,搜索像“Stephen King”這樣的單詞桦山,確保得到一些結(jié)果攒射,其中包括《恐怖大師》的有聲讀物!如果你想知道為什么這個特定的搜索詞,我們要找有聲讀物恒水,因為有聲讀物是數(shù)據(jù)結(jié)構(gòu)變化的一種類型……

顯示產(chǎn)品類型

搜索結(jié)果可能包括播客会放、歌曲或其他相關(guān)產(chǎn)品。讓表格視圖顯示它所顯示的產(chǎn)品類型是很有用的钉凌。

仍然在SearchResult.swift中鸦概,添加以下計算屬性:

var type: String {
  return kind ?? "audiobook"
}

var artist: String {
    return artistName ?? ""
} 

記住,如果項目類型是audiobook甩骏,而我們將artistName標記為可選,用這些新的計算性質(zhì)來對應(yīng)窗市。

?打開SearchViewController.swift并在tableView(_:cellForRowAt:)中,改變設(shè)置單元格的行饮笛。以下是藝術(shù)家的名字:

if searchResult.artist.isEmpty {
  cell.artistNameLabel.text = "Unknown"
} else {
  cell.artistNameLabel.text = String(format: "%@ (%@)", 
            searchResult.artist, searchResult.type)
}

第一個變化是現(xiàn)在檢查SearchResult的artist是否為空咨察。在測試該應(yīng)用程序時,我注意到有時搜索結(jié)果不包含藝術(shù)家的名字福青。在這種情況下摄狱,讓單元格顯示“Unknown”脓诡。

您還將new type屬性的值添加到藝術(shù)家名稱標簽中,該屬性應(yīng)該告訴用戶他們正在查看的是哪種產(chǎn)品:

這有一個問題媒役。kind的值直接來自服務(wù)器祝谚,它更像是一個內(nèi)部名稱,而不是您希望直接顯示給用戶的東西酣衷。

如果你想讓它說“Movie”交惯,或者你想把這個應(yīng)用翻譯成另一種語言——你稍后會在StoreSearch中做些什么。最好將這個內(nèi)部標識符“feature-movie”轉(zhuǎn)換為要顯示給用戶的文本“Movie”穿仪。

?用這個替換SearchResult.swif中type的計算屬性:

var type:String {
  let kind = self.kind ?? "audiobook"
  switch kind {
  case "album": return "Album"
  case "audiobook": return "Audio Book"
  case "book": return "Book"
  case "ebook": return "E-Book"
  case "feature-movie": return "Movie"
  case "music-video": return "Music Video"
  case "podcast": return "Podcast"
  case "software": return "App"
  case "song": return "Song"
  case "tv-episode": return "TV Episode"
  default: break
  }
  return "Unknown"
}

這些都是這個應(yīng)用程序能夠理解的產(chǎn)品類型席爽。

有可能我漏掉了一個,或者iTunes商店在某個時候添加了一個新產(chǎn)品類型啊片。如果發(fā)生這種情況只锻,switch切換到default:case,你只需要返回一個字符串紫谷,上面寫著“Unknown”——希望能在應(yīng)用程序的更新中幫助識別和修復未知類型齐饮。

default和break

Switch語句通常有一個default:case 在最后只顯示break。
在Swift中笤昨,switch必須是詳盡的沈矿,這意味著它必須對所查看的對象的所有可能值都有一個case。
這里你看到的是kind咬腋。Swift需要知道當kind不是任何已知值時該怎么做。這就是為什么需要包含default: case睡互,作為其他任何類型的可能值的集合根竿。
順便說一下:與其他語言不同,Swift中的case語句不需要在結(jié)尾處使用break就珠。它們不會像Objective-C那樣自動地從一種情況切換到另一種情況寇壳。

現(xiàn)在項目類型不應(yīng)該顯示為來自web服務(wù)的值,而應(yīng)該顯示為您為每個項目類型設(shè)置的值:

運行該應(yīng)用程序妻怎,搜索軟件壳炎、音頻書籍或電子書,看看解析代碼是否有效逼侦。由于商店里有大量的商品匿辩,你可能需要幾次嘗試才能找到一些。

稍后榛丢,你會添加一個控件铲球,讓你選擇想要搜索的產(chǎn)品類型,這讓你更容易找到電子書或有聲讀物晰赞。


5. 搜索結(jié)果排序

按字母順序排列搜索結(jié)果會很好稼病。這其實很簡單选侨。一個Swift數(shù)組已經(jīng)有了自己的排序方法。你所要做的就是告訴它要分類什么然走。

?SearchViewController.swift援制,在searchBarSearchButtonClicked(_:)中,在調(diào)用parse(data:)后添加以下內(nèi)容:

searchResults.sort(by: { result1, result2 in
  return result1.name.localizedStandardCompare(
         result2.name) == .orderedAscending
})

獲取結(jié)果數(shù)組后芍瑞,對searchResults數(shù)組調(diào)用sort(by:)晨仑,并使用一個閉包確定排序規(guī)則。這與在檢查表中對待辦事項列表進行排序是相同的啄巧。

為了對searchResults數(shù)組的內(nèi)容進行排序寻歧,閉包將相互比較SearchResult對象,如果result1在result2之前秩仆,則返回true码泛。在不同對SearchResult對象上重復調(diào)用閉包,直到數(shù)組完全排序澄耍。

這兩個對象的比較使用localizedStandardCompare()來比較SearchResult對象的名稱噪珊。因為您使用了. orderedrising,所以只有當result1.name位于result2.name之前時齐莲,閉包才返回true痢站。

運行應(yīng)用程序并驗證搜索結(jié)果是按字母順序排列的。


排序很容易添加选酗,但還有一種更簡單的方法阵难。

改進排序代碼

?修改你剛剛添加的排序代碼:

searchResults.sort { $0.name.localizedStandardCompare($1.name) 
                     == .orderedAscending }

它使用尾隨閉包語法將閉包放在方法名之后,而不是放在傳統(tǒng)的()括號內(nèi)作為參數(shù)芒填。這是可讀性上的一個小進步呜叫。

更重要的是,在閉包中不再按名稱引用兩個SearchResult對象殿衰,而是作為特殊的0和1變量朱庆。在Swift閉包中,使用這種簡寫而不是完整的參數(shù)名是很常見的闷祥。這也不再有返回語句娱颊。

?驗證一下這是否有效。

信不信由你凯砍,你可以做得更好箱硕。Swift有一個非常酷的功能悟衩,叫做操作符重載(operator overloading)颅痊。它允許您使用諸如+或*之類的標準操作符,并將它們應(yīng)用于您自己的對象局待。你甚至可以創(chuàng)建全新的運算符符號斑响。

過度使用這個特性并讓操作符做一些完全出乎意料的事情不是一個好主意——不要超載/不要做乘法菱属。-但它對分類很方便。

?打開SearchResult.swift并在class外添加以下代碼:

func < (lhs: SearchResult, rhs: SearchResult) -> Bool {
  return lhs.name.localizedStandardCompare(rhs.name) == 
         .orderedAscending
}

這看起來應(yīng)該很眼熟!您正在創(chuàng)建一個名為 < 的函數(shù)舰罚,該函數(shù)包含與前面的閉包相同的代碼纽门。這一次,左邊和右邊的SearchResult對象分別稱為lhs和rhs营罢。

現(xiàn)在赏陵,您已經(jīng)重載了小于操作符,因此它接受兩個SearchResult對象饲漾,如果第一個對象在第二個對象之前蝙搔,則返回true,否則返回false考传。像這樣:

searchResultA.name = "Waltz for Debby"
searchResultB.name = "Autumn Leaves"

searchResultA < searchResultB  // false
searchResultB < searchResultA  // true

回到SearchViewController.swift吃型,將排序代碼改為:

searchResults.sort { $0 < $1 }

這是很sweet的。使用 < 操作符可以非常清楚地看出僚楞,您正在按升序?qū)?shù)組中的項進行排序勤晚。
但是等一下,你可以寫得更短:

searchResults.sort(by: <)

哇泉褐,沒有比這更簡單的了!”這一行的字面意思是“按升序排列這個數(shù)組”赐写。當然,這只是因為您添加了自己的func <來重載小于操作符膜赃,所以它接受兩個SearchResult對象并對它們進行比較挺邀。

再次運行應(yīng)用程序,確保一切都是有序的跳座。

練習:看看你能否讓應(yīng)用程序按藝術(shù)家的名字排序端铛。
練習:試著按降序排序,從Z到a躺坟。提示:使用>操作符。

太好了!您讓應(yīng)用程序與web服務(wù)對話乳蓄,并且能夠?qū)⒔邮盏降臄?shù)據(jù)轉(zhuǎn)換為您自己的數(shù)據(jù)模型對象咪橙。

這個應(yīng)用程序可能不支持iTunes商店中顯示的所有產(chǎn)品,但我希望它能說明一個原則虚倒,即如何將以略微不同形式出現(xiàn)的數(shù)據(jù)轉(zhuǎn)換成更便于在自己的應(yīng)用程序中使用的對象美侦。

您可以隨意挖掘web服務(wù)API文檔,添加iTunes商店銷售的其他項目:https://ate.itunes.apple.com/resources/documentation/itunes-store-webservice -search-api/

?用諸如“使用同步網(wǎng)絡(luò)請求從web服務(wù)中添加抓取數(shù)據(jù)”這樣的信息commit你的修改魂奥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菠剩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子耻煤,更是在濱河造成了極大的恐慌具壮,老刑警劉巖准颓,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異棺妓,居然都是意外死亡攘已,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門怜跑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來样勃,“玉大人,你說我怎么就攤上這事性芬∠靠簦” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵植锉,是天一觀的道長辫樱。 經(jīng)常有香客問我,道長汽煮,這世上最難降的妖魔是什么搏熄? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮暇赤,結(jié)果婚禮上心例,老公的妹妹穿的比我還像新娘。我一直安慰自己鞋囊,他們只是感情好止后,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溜腐,像睡著了一般译株。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挺益,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天歉糜,我揣著相機與錄音,去河邊找鬼望众。 笑死匪补,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的烂翰。 我是一名探鬼主播夯缺,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼甘耿!你這毒婦竟也來了踊兜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤佳恬,失蹤者是張志新(化名)和其女友劉穎捏境,沒想到半個月后于游,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡典蝌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年曙砂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骏掀。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸠澈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出截驮,到底是詐尸還是另有隱情笑陈,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布葵袭,位于F島的核電站涵妥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏坡锡。R本人自食惡果不足惜蓬网,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹉勒。 院中可真熱鬧帆锋,春花似錦、人聲如沸禽额。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脯倒。三九已至实辑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藻丢,已是汗流浹背剪撬。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留悠反,地道東北人残黑。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像问慎,于是被迫代替她去往敵國和親萍摊。 傳聞我的和親對象是個殘疾皇子挤茄,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

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

  • #愛的五種語言# 記錄愛箱滿滿的一天 1. 一大早去體檢如叼,一切順利,遇見熱情體貼的體檢人員穷劈,開心 2. 老公今天一...
    雙子座的尾巴閱讀 155評論 0 0
  • 現(xiàn)在寫的是昨天到此刻笼恰。 昨天她神奇的要去ex那拿回端午節(jié)放在他那的兩千塊錢踊沸,跟她商量時她又說氣話,什么不要了之類的...
    燃燒羊羊閱讀 219評論 3 1
  • 說出來你可能不信社证,我從記事起就是那種別人家的孩子逼龟。一歲半開始寫字背詩,早早開始上幼兒園追葡,是老師們最得意的乖學生腺律。什...
    食愛女閱讀 216評論 0 1
  • 主題: 盤點2018,寄語2019. ---攜手戰(zhàn)友宜肉,不忘初心匀钧,砥礪前行。 時間: 2019.1.1 07:00a...
    CoryLiu閱讀 250評論 0 1