取得遠端 API 資料並儲存
這一小節會介紹如何取得遠端 API 的資料,並將資料儲存成本地檔案,以供後續使用。
我們使用 Data.Taipei 臺北市政府資料開放平台 的 API 資料作為示範,這個平台開放的資料有很多項,這邊以取得臺北市臺北旅遊網-住宿資料(中文)及臺北市臺北旅遊網-景點資料(中文)作為示範。
進入資料頁後,點擊 使用資料 > API ,如下圖:
接著會進入到 API 說明頁,看到下圖標示起來的網址,便是這個 API 的資料:
將上圖中標示的網址以瀏覽器開啟後,會發現取得的資料是 JSON 格式,可以使用桌機的 Chrome 瀏覽器的擴充套件 JSONView 來將這個 JSON 資料結構化呈現在畫面中,比較方便檢視內容,如下圖:
以上為前置作業的介紹,接著會開始介紹如何在應用程式中獲取遠端 API 資料,並將這資料儲存為本地的一個 JSON 檔案,最後會說明如何解析這個 JSON 檔案並讀取其內資訊。
Hint
- JSON 是一種輕量級的資料交換格式,以純文字為基礎來儲存與傳送結構資料,可以經由特定的格式儲存任何文字資料(像是字串、數字、陣列或物件), JSON 可以讓你很簡單的與其他程式交換資料。
- 有些 API 資料會需要事先向擁有者申請 ID ,以便在接收資料前辨別獲取資料者的身分。
首先在 Xcode 裡,新建一個 Single View Application 類型的專案,取名為 ExFetchDataAndStorage 。
一開始先為ViewController
建立三個屬性:
class ViewController: UIViewController {
var taipeiDataUrl :String!
var documentsPath :String!
var touringSiteTargetUrl :String!
// 省略
}
以及在viewDidLoad()
中設置儲存檔案的目錄路徑與 API 網址,以供後續使用,如下:
// 應用程式儲存檔案的目錄路徑
let urls =
NSFileManager.defaultManager().URLsForDirectory(
.DocumentDirectory, inDomains: .UserDomainMask)
self.documentsPath = urls[urls.count-1].absoluteString
self.taipeiDataUrl =
"http://data.taipei/opendata/datalist"
+ "/apiAccess?scope=resourceAquire&rid="
獲取遠端 API 資料
應用程式中要與遠端交換資料必須使用 NSURLSession 相關函式庫,這邊會介紹兩種方式。
在介紹如何獲取資料之前,請先了解 iOS 9 之後預設為只能載入 https 的網頁(也就是加密過的),要如何設定成可開啟 http 網頁請參考前面章節無法載入 http 的網址的說明。
基本獲取遠端資訊方式
先介紹基本獲取方式,這種方式沒有用到委任模式,會單純的下載遠端檔案下來以供使用,下面將其寫在一個方法中:
func simpleGet(myUrl :String, targetPath :String) {
if let url = NSURL(string: myUrl) {
NSURLSession.sharedSession()
.dataTaskWithURL(url) {
data, response, error in
print(
NSString(
data: data!,
encoding: NSUTF8StringEncoding))
}.resume()
}
}
上述程式中,先使用NSURLSession.sharedSession()
獲得一個共用的 NSURLSession 實體以供連線,接著帶入要接收資料的網址url
到dataTaskWithURL()
方法,最後帶一個閉包來處理獲得的資料。
請注意到後面還接了一個方法resume()
,因為這個連線必須手動執行,所以在設置完後必須接著使用方法resume()
來送出連線。
閉包的第一個參數data
便為獲得的資訊,這邊先轉成字串印出來,後續會再做更多處理。
普通獲取遠端資訊方式
如果想要在下載資料的各個階段執行動作,就需要實作委任方法,首先為ViewController
加上委任模式需要遵循的協定:
class ViewController: UIViewController,
NSURLSessionDelegate, NSURLSessionDownloadDelegate {
// 省略
}
接著是可以實作的委任方法:
// 下載完成
func URLSession(
session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didFinishDownloadingToURL location: NSURL) {
print("下載完成")
}
// 下載過程中
func URLSession(
session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
// 如果 totalBytesExpectedToWrite 一直為 -1
// 表示遠端主機未提供完整檔案大小資訊
print("下載進度: \(totalBytesWritten)")
print("/\(totalBytesExpectedToWrite)")
}
最後將獲取方式寫在一個方法中:
// 普通獲取遠端資訊的方式
func normalGet(myUrl :String) {
if let url = NSURL(string: myUrl) {
// 設置為預設的 session 設定
let sessionWithConfigure =
NSURLSessionConfiguration.
defaultSessionConfiguration()
// 設置委任對象
let session = NSURLSession(
configuration: sessionWithConfigure,
delegate: self,
delegateQueue: nil)
// 設置遠端 API 網址
let dataTask =
session.downloadTaskWithURL(url)
// 執行動作
dataTask.resume()
}
}
上述程式首先使用NSURLSessionConfiguration
設置一個 session 的設定,並使用defaultSessionConfiguration()
設置為預設模式。另外還可以使用ephemeralSessionConfiguration()
,這個模式不會將連線中的快取、Cookie 或認證資訊做儲存,就像是瀏覽器的隱私模式。或是使用backgroundSessionConfiguration()
,讓應用程式被切換到背景時仍然可以執行連線工作。
接著使用NSURLSession(configuration:delegate:delegateQueue:)
設置一個 NSURLSession 實體(相較於基本獲取方式的共用實體,這邊設置為一個新的 NSURLSession 實體。),參數傳入前面設置的 session 設定,以及設置委任對象。設置委任對象後,在下載過程中與完成時,都會執行委任方法。
最後使用downloadTaskWithURL()
填入遠端 API 網址,及執行動作resume()
。
執行獲取資訊
在viewDidload()
中,執行獲取兩個示範 API 資料,分別使用前面介紹的基本獲取資訊方式與普通獲取資訊方式:
// 台北住宿資料 中文
let strHotelID =
"6f4e0b9b-8cb1-4b1d-a5c4-febd90f62469"
self.simpleGet(taipeiDataUrl + strHotelID,
targetPath: self.documentsPath + "hotel.json")
// 台北景點資料 中文
let strTouringSiteID =
"36847f3f-deff-4183-a5bb-800737591de5"
self.touringSiteTargetUrl =
self.documentsPath + "touringSite.json"
self.normalGet(taipeiDataUrl + strTouringSiteID)
以上便會開始獲取遠端資料。方法simpleGet()
的第二個參數targetPath
後續會再做說明使用。
儲存為本地檔案
在前面順利獲得資料後,接著將資料存成本地檔案以供後續使用。
首先是稍前建立的方法simpleGet()
,獲得的資料data
為 NSData 型別,以下為儲存方式:
// 建立檔案
let fileurl = NSURL(string: targetPath)
if let result = data?.writeToURL(fileurl!, atomically: true) {
if result {
print("簡單方式獲取遠端資訊:儲存資訊成功")
} else {
print("簡單方式獲取遠端資訊:儲存資訊失敗")
}
}
方法simpleGet()
傳入的第二個參數targetPath
型別為 String ,所以先將其轉為 NSURL ,並帶入型別為 NSData 的閉包參數data
的方法writeToURL()
,以建立一個新的本地檔案。會返回一個 Bool? 的值表示儲存成功或失敗。
接著是方法normalGet()
,會在下載完成的委任方法中獲得資料location
,其型別為 NSURL,是一個本地的暫存檔案路徑,以下為儲存方式:
let targetUrl = NSURL(
string: self.touringSiteTargetUrl)!
let data = NSData(contentsOfURL: location)
if ((data?.writeToURL(targetUrl, atomically: true))
!= nil) {
print("普通獲取遠端資訊的方式:儲存資訊成功")
} else {
print("普通獲取遠端資訊的方式:儲存資訊失敗")
}
使用一開始設置的屬性touringSiteTargetUrl
來生成一個 NSURL ,用來表示新的本地檔案路徑。接著將location
轉為型別 NSData 的資料後,再以方法writeToURL()
建立一個新的本地檔案。
以上如果都順利儲存成功,會在本地的 Documents 目錄中,分別建立 hotel.json 與 touringSite.json 檔案。
解析 JSON 檔案
前面建立好兩個 JSON 檔案後,必須再將其做解析以取得其內的資料。首先使用先前介紹的瀏覽器擴充套件檢視一下這個 JSON 內容:
可以發現格式如下:
{
result: {
offset: 0,
limit: 10000,
count: 517,
sort: "",
results: [
// 省略
]
}
}
最外層可以轉換成一個字典( Dictionary ),其內只有一筆資料, key 值為result
,對應著其內的資料也是一個字典,其內有五筆資料,代表意思分別為:
- offset:獲取資料的偏移量,如果設置為 3 ,則表示獲取資料要跳過前面 3 筆,從第四筆開始取得。
- limit:獲取資料的最多數量,如果設置為 10 ,則最多只會取得 10 筆資料。
- count:全部的資料數量。
- sort:排序方式,與 SQL 指令類似,如果設置為 "id asc, RowNumber desc",則是以 id 從小到大排序,以及以 RowNumber 從大到小排序。
- results:獲取的資料,會依照前面設置的設定取得資料。
如果要使用這些功能,可以在稍前提到的 API 網址後面加上,像是要設置 offset 為 5 以及 limit 為 10 ,則是在該網址後面加上&offset=5&limit=10
即可。
依照上述的格式,可以將其轉換為一個型別為 [String:[String:AnyObject]] 的字典,以供後續使用。接著這邊將解析 JSON 的功能寫在一個方法中,如下:
// 解析 json 檔案
func jsonParse(url :NSURL) {
do {
let dict =
try NSJSONSerialization
.JSONObjectWithData(
NSData(contentsOfURL: url)!,
options:
NSJSONReadingOptions.AllowFragments)
as! [String:[String:AnyObject]]
print(dict.count)
let dataArr =
dict["result"]!["results"] as! [AnyObject]
print(dataArr.count)
print(dataArr[3]["stitle"])
} catch {
print("解析 json 失敗")
}
}
上述程式使用NSJSONSerialization.JSONObjectWithData()
來解析 JSON 檔案,因為設計為一個拋出函式,所以使用do-catch
語句來定義錯誤的捕獲。
以上即為這小節範例的內容。
範例
本節範例程式碼放在 apps/taipeitravel