(Swift) iOS Apps with REST APIs(二)

重要說明: 這是一個系列教程,非本人原創(chuàng)蜈缤,而是翻譯國外的一個教程。本人也在學(xué)習(xí)Swift冯挎,看到這個教程對開發(fā)一個實際的APP非常有幫助底哥,所以翻譯共享給大家。原教程非常長房官,我會陸續(xù)翻譯并發(fā)布趾徽,歡迎交流與分享。

我已準(zhǔn)備好到直接跳到了一些對你有用的代碼翰守。但是孵奶,蘋果在iOS9中引入App Transport Security,雖然ATS是保護(hù)從你iPhone中發(fā)送和接受的數(shù)據(jù)蜡峰,但對于開發(fā)人員來說的確有點頭痛了袁。

ATS要求使用SSL進(jìn)行數(shù)據(jù)傳輸,這對實施來說是非常挑剔的湿颅≡芈蹋可悲的是,現(xiàn)在很多服務(wù)器是不滿足這項要求的肖爵。所以卢鹦,如果我們需要與訪問這些服務(wù)器時該怎么解決呢?接下來我們會解決這個問題劝堪,因為后面講解網(wǎng)絡(luò)調(diào)用基礎(chǔ)知識所使用的服務(wù)器是需要先解決這個問題的冀自。

我們將為這種應(yīng)用情況下的應(yīng)用程序傳輸安全增加一個異常。雖然我們可以禁用ATS秒啦,但這樣比只為一臺服務(wù)器創(chuàng)建異常更安全熬粗。在本章我們所要訪問的API是http://jsonplaceholder.typicode.com/

我們需要在項目的info.plist文件中增加一些鍵余境,以便創(chuàng)建該異常驻呐。我們會添加一個NSAppTransportSecurity的字典,該字典中包含NSExceptionDomains字典芳来,存放服務(wù)器:jsonplaceholder.typicode.com(注意:沒有結(jié)尾的斜杠含末,也沒有http或https前綴)。在jsonplaceholder.typicode.com字典中增加一個布爾值的條目NSThirdPartyExceptionAllowsInsecureHTTPLoads即舌,并把值設(shè)置為YES
<div style="text-align: center">


</div>

這樣佣盒,下面我們就可以正式開始編寫代碼了。

使用Swift進(jìn)行簡單的REST API調(diào)用

當(dāng)今幾乎每一個APP都會通過API獲取或創(chuàng)建內(nèi)容顽聂。在本書中肥惭,我們會使用一個非常強(qiáng)大的網(wǎng)絡(luò)處理庫Alamofire盯仪,當(dāng)然你也可以使用NSURLSession快速的,不是那么優(yōu)雅的執(zhí)行異步數(shù)據(jù)請求任務(wù)蜜葱。

Swift進(jìn)行異步URL請求的方法如下:

  public func dataTaskWithRequest(request: NSURLRequest,    
    completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void)    
    -> NSURLSessionDataTask    

該方法將向特定的URL發(fā)起一個請求全景,請求發(fā)送完畢后將停止運行。一旦得到對方服務(wù)器的響應(yīng)(甚至是一個錯誤報告)牵囤,相應(yīng)的完成處理程序(completion handler)將被調(diào)用爸黄。我們可以在完成處理程序中對調(diào)用的結(jié)果進(jìn)行處理,包括錯誤檢查奔浅、將數(shù)據(jù)保存到本地馆纳、更新界面诗良,等等汹桦。在實現(xiàn)dataTaskWithRequest章節(jié)我們會深入討論完成處理程序。

最簡單的例子就是一個GET請求鉴裹。不過舞骆,我們需要一個API來進(jìn)行測試,幸運的是JSONPlaceholder可以充當(dāng)這個工具径荔。

JSONPlaceholder是一個用來測試和制作原型的在線偽REST API督禽。就像網(wǎng)頁中的圖像占位符。

JSONPlaceholder中有一些資源可以使用总处,類似于我們在很多應(yīng)用中使用的:用戶狈惫、帖子、照片鹦马、相冊...等等胧谈。不過我們這里使用帖子(posts)。

首先荸频,讓我們先打印出第一個帖子的標(biāo)題菱肖。我們可以通過發(fā)起一個到posts端點(endpoint)的GET請求,參數(shù)為帖子的ID旭从,來獲取一個帖子的詳細(xì)信息稳强。從http://jsonplaceholder.typicode.com/posts/我們可以知道第一個帖子的ID是1。下面就讓我們獲取它:

首先和悦,我們設(shè)置URL請求:

  let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
  guard let url = NSURL(string: postEndpoint) else {
    print("Error: cannot create URL")
    return
  }
  let urlRequest = NSURLRequest(URL: url)

guard關(guān)鍵字可以讓我們檢查所請求的URL是否有效退疫。

接下來,使用NSURLSession發(fā)送請求:

  let config = NSURLSessionConfiguration.defaultSessionConfiguration()
  let session = NSURLSession(configuration: config)

然后鸽素,創(chuàng)建數(shù)據(jù)任務(wù)(data task):

  let task = session.dataTaskWithRequest(urlRequest, completionHandler: nil)

最后褒繁,發(fā)送請求(是的,這個方法名稱有點怪異)

  task.resume()

現(xiàn)在我們將調(diào)起URL(通過urlRequest)付鹿,并獲得相應(yīng)的結(jié)果(作為默認(rèn)將使用GET請求)澜汤。因此我們必須實現(xiàn)一個完成處理程序?qū)Ψ祷亟Y(jié)果進(jìn)行處理蚜迅,以便完成相應(yīng)的處理。

當(dāng)你第一次運行時俊抵,完成處理程序可能會有點混亂谁不。一方面,它是一個變量或參數(shù)徽诲;另一方面刹帕,它又是一堆代碼。如果你以前沒有用過這種程序(塊或者閉包)谎替,你會覺得非常奇怪偷溺。

當(dāng)你的應(yīng)用需要花費一些時間進(jìn)行任務(wù)處理,比如:API調(diào)用钱贯,并且在任務(wù)處理完畢后需要執(zhí)行某些動作處理挫掏,如更新界面顯示新的數(shù)據(jù),這時候使用完成處理程序是超級方便的秩命。接下來你會看到蘋果的API尉共,如dataTaskWithRequest中的完成處理程序,及后面我們在使用我們自己API時的完成處理程序弃锐。

dataTaskWithRequest中的完成處理程序的方法簽名如下:

  completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void

這是一段代碼塊(->將告訴我們返回值類型袄友,如果有的話)。它有三個參數(shù):(NSData?, NSURLResponse?, NSError?)及返回類型:Void霹菊。對于一個具體的完成處理程序的內(nèi)聯(lián)代碼如下:

  let task = session.dataTaskWithRequest(urlRequest, completionHandler: 
  { (data, response, error) in
    // 這里是完成處理程序的具體代碼
  })  
  task.resumt()

注意剧蚣,花括號中間的那段代碼,有三個參數(shù)(data, response, error)旋廷,與完成處理程序中聲明的參數(shù)(NSData?, NSURLResponse?, NSError?)是對應(yīng)的鸠按。你可以在代碼段顯示的指出,也可以省略柳洋,這個不是必須的待诅,因為編譯器可以推導(dǎo)出來。所要記住的是熊镣,代碼不僅僅是給電腦執(zhí)行的卑雁,更多的時候是給我們自己看,因此绪囱,不要弄得太晦澀测蹲。

  let task = session.dataTaskWithRequest(urlRequest, 
    completionHandler:{(data: NSData?, response: NSURLResponse?, 
      error: NSError?) in 
    // 這里是完成處理程序的具體代碼
    print(response)
    print(error)
  })
  task.resume()

有點混亂是嗎!實際上你完全可以刪除completionHandler聲明鬼吵,直接將代碼放在函數(shù)調(diào)用的后面扣甲。這完全和上面的代碼是一樣的,這種寫法在Swift是非常常見的(譯者注:這種在Swift中稱為尾隨閉包):

  let task = session.dataTaskWithRequest(urlRequest) { (data, response, error) in 
    // 完成處理程序?qū)⒎旁谶@里
    print(response)
    print(error)
  }
  task.resume()

如果將參數(shù)名稱設(shè)置為_就是告訴編譯器,你會忽略掉該參數(shù):

  let task = session.dataTaskWithRequest(urlRequest) { (data, _, error) in
    // 這時候?qū)⒉荒軌虼蛴esponse琉挖,因為我們忽略了該參數(shù)
    print(error)  
  }
  task.resume()

我們也可以將完成處理函數(shù)聲明為一個變量启泣,然后把這個變量傳給dataTaskWithRequest。這樣示辈,當(dāng)我們需要在多個地方調(diào)用時會非常方便寥茫。在后面實現(xiàn)OAuth2.0認(rèn)證流程時我們會使用這種方式,因為還有很多要處理矾麻,所以現(xiàn)在我們會在調(diào)用失敗處理的時候使用纱耻。

下面的代碼就是完成處理程序的變量方式:

  let myCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> Void = {
    (data, response, error) in 
       // 完成處理代碼
       print(response)
       print(error)
  }
  let task = session.dataTaskWithRequest(urlRequest, 
    completionHandler: myCompletionHandler)
  task.resume()

那么上面這段代碼發(fā)生了什么呢?嗯险耀,當(dāng)我們調(diào)用dataTaskWithRequest函數(shù)后弄喘,它不會馬上被執(zhí)行。但甩牺,蘋果在實現(xiàn)dataTaskWithRequest會像下面調(diào)用:

  public func dataTaskWithRequest(request: NSURLRequest,
    completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void)
    -> NSURLSessionDataTask {
    // 進(jìn)行URL請求
    // 等待請求結(jié)果
    // 檢查返回是否有錯誤蘑志,并進(jìn)行處理
    completionHandler(data, response, error)
    // 返回 data task
 }

當(dāng)然,在你的代碼中不用像上面那樣去寫柴灯,因為dataTaskWithRequest中已經(jīng)實現(xiàn)卖漫。事實上,可能會有幾個類似上面的調(diào)用來處理成功或者失敗赠群。而完成處理程序僅僅就是待在那里,等待dataTaskWithRequst處理完畢后調(diào)用旱幼。

那么什么是完成處理程序呢查描?簡單來說,就是我們可以用來在一個動作完成后再進(jìn)行某些處理柏卤。比如冬三,像上面我們用來打印調(diào)用的結(jié)果,及可能出現(xiàn)的錯誤缘缚,以便驗證API調(diào)用是否正常工作勾笆。讓我們回到我們例子中,在完成處理程序?qū)懸稽c有用的代碼桥滨。然后窝爪,代碼將變成:

  let task = session.dataTaskWithRequest(urlRequest, completionHandler:
    { (data, response, error) in
    // 做一些有意義的事
  })
  task.resume()

現(xiàn)在我們可以訪問3個對象:請求的響應(yīng),請求的返回數(shù)據(jù)及請求的錯誤(如果有的話)齐媒。下面我們首先進(jìn)行錯誤檢查蒲每,然后給出如何得到我們想要的數(shù)據(jù):第一篇帖子的標(biāo)題。接下來我們將:

  • 確定我們得到了數(shù)據(jù)喻括,并且沒有錯誤邀杏;
  • 嘗試將數(shù)據(jù)轉(zhuǎn)換為JSON格式(因為,API返回的數(shù)據(jù)格式是JSON)唬血;
  • 獲取帖子的標(biāo)題并打印望蜡。

注意唤崭,這里你需要在代碼中增加import Foundation,以便可以使用NSJSONSerialization脖律。

  let task = session.dataTaskWithRequest(urlRequest, completionHandler): {
    (data, response, error) in 
    guard let responseData = data else {
      print("錯誤: 沒有接收到返回數(shù)據(jù)")
      return
    }
    guard error == nil else {
      print("獲取/posts/1時有錯誤")
      print(error)
      return
    }
    
    // 將返回的數(shù)據(jù)解析為JSON格式
    let post: NSDictionary
    do {
      post = try NSJSONSerialization.JSONObjectWithData(responseData, 
        options:[]) as! NSDictionary
    } catch {
      print("將數(shù)據(jù)轉(zhuǎn)換為JSON時出錯")
      return
    }
    
    // 現(xiàn)在我們可以訪問post
    print("帖子:" + post.description)
    
    // 因為post是一個dictionary(字典)浩姥,因此我們可以通過"title"來獲取帖子標(biāo)題
    if let postTitle = post["title"] as? String {
      print("帖子標(biāo)題: " + postTitle)
    }
  })
  task.resume()

運行后,輸出如下:

  帖子: {
    body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderi\
t molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"; 
    id = 1;
    title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit";
    userId = 1;
  }
  帖子標(biāo)題: sunt aut facere repellat provident occaecati excepturi optio reprehenderit

這個有點冗長状您,假如你僅僅需要快速發(fā)起一個GET請求勒叠,并且不需要身份驗證,那么就是這樣膏孟。

但如果請求的方法類型不是GET眯分,那么你需要使用一個可變的NSURLRequest,通過NSURLRequest就可以設(shè)置請求的方法了:

  let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts"
  let postUrlRequest = NSMutableURLRequest(URL: NSURL(string: postEndpoint)!)
  postUrlRequest.HTTPMethod = "POST"

這樣我們就是將新建的帖子內(nèi)容設(shè)置給請求的HTTPBody中:

  let newPost: NSDictionary = ["title": "Frist Post", "body": "I is fisrt", 
    "userId":1]
  do {
    let jsonPost = try NSJSONSerialization.dataWithJSONObject(newPost, options:[])
    postUrlRequest.HTTPBody = jsonPost
  } catch {
    print("錯誤:無法創(chuàng)建帖子的JSON對象")
  }

現(xiàn)在我們就可以執(zhí)行請求了(假設(shè)我們現(xiàn)在還持有前面創(chuàng)建的會話信息):

  let task = session.dataTaskWithRequest(postUrlRequest, completionHandler: nil)
  task.resume()

如果上面的代碼正常工作柒桑,那么我們在發(fā)送后將會得到新帖子的ID弊决。因為這只是用于測試,JSONPlaceholder會讓你做各種REST請求(GET魁淳,POST飘诗,PUT,PATCH界逛,DELETE及OPTIONS)昆稿,但不會真正改變數(shù)據(jù)。所以息拜,當(dāng)我們新建了帖子后溉潭,我們會得到新帖子的ID,以確認(rèn)我們程序工作正常少欺,但它實際上不會保存到數(shù)據(jù)庫中喳瓣,所以我們是不能訪問它的。

  let newPost: NSDictionary = ["title": "Frist Post", 
    "body": "I is fisrt", "userId":1]
  do {
    let jsonPost = try NSJSONSerialization.dataWithJSONObject(newPost, 
      options:[])
    postUrlRequest.HTTPBody = jsonPost
    
    let config = NSURLSessionConfiguration.defaultSessionConfiguration()
    let session = NSURLSession(configuration: config)
    
    let task = session.dataTaskWithRequest(postsUrlRequest, 
      completionHandler: { (data, response, error) in
      guard let responseData = data else {
        print("錯誤:沒有接收到數(shù)據(jù)")
        return
      }
      guard error == nil else {
        print("調(diào)用POST /posts 時出現(xiàn)錯誤")
        print(error)
        return
      }
      
      // 解析成JSON
      let post: NSDictionary
      do {
        post = try NSJSONSerialization.JSONObjectWithData(responseData,
          options:[]) as! NSDictionary
      } catch {
        print("解析POST /posts的返回時出錯")
        return
      }
      
      // 打印帖子內(nèi)容
      print("帖子:" + post.description)
    
      // 打印帖子的ID
      if let postID = post["id"] as? Int {
      print("帖子ID:\(postID)")
    }
  })
  task.resume()
}  

刪除帖子的代碼非常類似(不需要帖子創(chuàng)建后的代碼):

  let firstPostEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
  let firstPostUrlRequest = NSMutableRequest(URL: NSURL(string: firstPostEndpoint)!)
  firstPostUrlRequest.HTTPMethod = "DELETE"
    
  let config = NSURLSessionConfiguration.defaultSessionConfiguration()
  let session = NSURLSession(configuration: config)
    
  let task = session.dataTaskWithRequest(firstPostUrlRequest,
    completionHandler: { (data, response, error) in
    guard let _ = data else {
      print("調(diào)用DELEE /posts/1 時出現(xiàn)錯誤")
      return
    }
  })
  task.resume()

這是使用Swift調(diào)用REST API快速的赞别、骯臟的方式畏陕。這里會有幾個陷阱:因為我們假設(shè)了調(diào)用會成功得到返回數(shù)據(jù),并且數(shù)據(jù)的格式是我們所期望的仿滔。如果這會引起一個問題惠毁,一旦返回值的類型不是字典類型,那么整個App就會崩潰堤撵。

  post = try NSJSONSerialization.JSONObjectWithData(responseData, 
    options:[] as! NSDictionary)

當(dāng)然仁讨,我們可以通過檢查返回的數(shù)據(jù)確保不為空并且是一個字典類型(但,這會讓冗長變的很快)实昨,或者我們使用SwiftyJSON來代替:

  // 解析成JSON
  let post = JSON(data: responseData)  
  if let postID = post["id"].int {
    print("帖子ID:\(postID)")
  }

SwiftyJSON在每一步都會進(jìn)行檢查洞豁,如果post為空,或者post["id"]為空,則postID也會為空丈挟。這時候.int將返回一個可選的刁卜,就像Int?。如果你確保值不會為空曙咽,那么可以使用.intValue蛔趴,而不是得到一個可選值。

SwiftyJSON不僅僅可以處理整數(shù)例朱。下面列出了如何處理string孝情、doubleboolean:

let title = myJSON["title"].string
let cost = myJSON["cost"].double
let isPurchased = myJSON["purchased"].bool

如果JSON對象是一個數(shù)組(比如:帖子列表),那么你可以通過數(shù)組索引來訪問響應(yīng)的屬性洒嗤,如:

let thirdPostTitle = posts[3]["title"].string

到目前為止箫荡,這些代碼非常冗長,而且也沒有什么抽象渔隶。你本來是在處理帖子的功能羔挡,卻不得不寫一大堆代碼來處理網(wǎng)絡(luò)請求及數(shù)據(jù)處理〖浒Γ看來Alamofire將是我們一個比較好的選擇:

Alamofire.request(.GET, postEndpoint).responseJSON { response in
  // 獲取錯誤
  print(response.result.error)
  // 獲取序列化后的數(shù)據(jù)(如JSON)
  print(response.result.value)
  // 獲取原始數(shù)據(jù)
  print(response.data)
  // 獲取NSHTTPURLResponse
  print(response.response)
}

獲取GitHub上的代碼:REST gists

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绞灼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子呈野,更是在濱河造成了極大的恐慌低矮,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件际跪,死亡現(xiàn)場離奇詭異商佛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)姆打,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肠虽,“玉大人幔戏,你說我怎么就攤上這事∷翱危” “怎么了闲延?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長韩玩。 經(jīng)常有香客問我垒玲,道長,這世上最難降的妖魔是什么找颓? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任合愈,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘佛析。我一直安慰自己益老,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布寸莫。 她就那樣靜靜地躺著捺萌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪膘茎。 梳的紋絲不亂的頭發(fā)上桃纯,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音披坏,去河邊找鬼态坦。 笑死,一個胖子當(dāng)著我的面吹牛刮萌,可吹牛的內(nèi)容都是我干的驮配。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼着茸,長吁一口氣:“原來是場噩夢啊……” “哼壮锻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涮阔,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤猜绣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后敬特,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掰邢,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年伟阔,在試婚紗的時候發(fā)現(xiàn)自己被綠了辣之。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡皱炉,死狀恐怖怀估,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情合搅,我是刑警寧澤多搀,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站灾部,受9級特大地震影響康铭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赌髓,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一从藤、第九天 我趴在偏房一處隱蔽的房頂上張望催跪。 院中可真熱鬧,春花似錦呛哟、人聲如沸叠荠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榛鼎。三九已至,卻和暖如春鳖孤,著一層夾襖步出監(jiān)牢的瞬間者娱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工苏揣, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留黄鳍,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓平匈,卻偏偏與公主長得像框沟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子增炭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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