Swift 中處理 JSON 數據有很多種方式,可以使用原生的 NSJSONSerialization,也可以使用很多第三方庫。原生的 NSJSONSerialization 方式這篇文章中介紹過。這次我們介紹一個第三方庫 SwiftyJSON
并且用它來制作一個有趣的 APP.
關于 SwiftyJSON
首先,我們來了解一下什么是 SwiftyJSON
, 并且我們為什么要用這個庫。比如我們要解析這個比特幣實時價格的接口:
這個接口的數據格式如下:
{
"time": {
"updated": "Jul 20, 2015 13:14:00 UTC",
"updatedISO": "2015-07-20T13:14:00+00:00",
"updateduk": "Jul 20, 2015 at 14:14 BST"
},
"disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD & CNY respectively).",
"bpi": {
"USD": {
"code": "USD",
"rate": "278.3400",
"description": "United States Dollar",
"rate_float": 278.34
},
"CNY": {
"code": "CNY",
"rate": "1,717.4683",
"description": "Chinese Yuan",
"rate_float": 1717.4683
}
}
}
如果我們使用原生的 NSJSONSerialization
方式,得到比特幣的人民幣價格的話,我們寫出的代碼大概就是這樣的:
var url = "http://api.coindesk.com/v1/bpi/currentprice/CNY.json"
if let jsonData = NSData(contentsOfURL: NSURL(string: url)!) {
if let jsonObj: NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableLeaves, error: nil) as? NSDictionary {
if let bpi:NSDictionary = jsonObj["bpi"] as? NSDictionary {
if let cny:NSDictionary = bpi["CNY"] as? NSDictionary {
print(cny["rate"]!)
}
}
}
}
那么我們再來看一下,我們用 SwiftyJSON 來達到同樣的目的要寫的代碼:
let url = "http://api.coindesk.com/v1/bpi/currentprice/CNY.json"
if let jsonData = NSData(contentsOfURL: NSURL(string: url)!) {
let json = JSON(data: jsonData)
print(json["bpi"]["CNY"]["rate"])
}
是不是感覺精簡了很多呢,對,就是這個效果。SwiftyJSON
的以大好處就是,不用你來處理 Swift 中的類型轉換,它會自動幫你處理類型等開發語言相關的問題,讓你專注于 JSON 數據的處理中。怎么樣,挺好用的把。

關于 SwifyJSON 的更多介紹,大家還可以參看它的 Github 主頁:
https://github.com/SwiftyJSON/SwiftyJSON
下面我們就以一個例子來繼續了解 SwiftyJSON。
比特幣查詢應用
我們今天要做的是一個比特幣實時價格的 APP,這里我們會用到 SwiftyJSON 來解析服務端的數據。
首先我們創建一個項目, Single View Application
類型:

然后設置好項目的基本信息:

然后就是要引入 SwiftyJSON
庫,
另外還可以下載我們預配置好的項目來進行開發:bitprice-start.zip
現在我們就進入主題吧,首先我們開始構建 UI 界面,打開 Main.storyboard
進行編輯。
- 首先,我們在
storyboard
中拖入三個UILabel

其中第一個 Label 的 text
屬性設置為 "當前價格", 后兩個 Label 的 text
設置為空,用作顯示比特幣的價格。
- 然后,我們將兩個用于顯示價格的
UILabel
鏈接到主控制器的Outlet
中,在打開 storyboard 視圖的同時,按住Option
并點擊ViewController.swift
。這樣編輯界面上同時顯示了storyboard
和控制器的代碼,然后我們在storyboard
中選中 Label,然后按住control
拖動到控制器的代碼中:

隨后會彈出一個變量名稱提示框,我們將第一個 UILabel 命名為 priceLabel
,將第二個 UILabel 命名為 differLabel
。

最后,我們在給 ViewController
建立一個新的屬性 lastPrice
, 存儲上次更新的價格,用于計算當前價格相對于上次的漲跌幅。
這樣我們的 ViewController
的屬性定義如下:
class ViewController: UIViewController {
@IBOutlet var priceLabel: UILabel!
@IBOutlet var differLabel: UILabel!
var lastPrice:Double = 0.0
}
兩個 IBOutlet
鏈接的 UILabel
, 還有一個 Double
變量用于存放上次的價格。
基礎結構設置好后,我們就可以開始構建應用的邏輯了,我們首先定義一個方法 getLatestPrice()
,用于獲取比特幣最新的價格:
func getLatestPrice() -> String?{
let url = "http://api.coindesk.com/v1/bpi/currentprice/CNY.json"
if let jsonData = NSData(contentsOfURL: NSURL(string: url)!) {
let json = JSON(data: jsonData)
return json["bpi"]["CNY"]["rate"].stringValue
}else {
return nil
}
}
這里面我們首先通過 NSData
的構造方法從指定的 URL 地址讀取了比特幣價格數據,然后用到了 SwiftyJSON
來讀取和解析返回的 JSON
數據
let json = JSON(data: jsonData)
return json["bpi"]["CNY"]["rate"].stringValue
只有兩行代碼,就完成了數據的提取,很方便吧。
數據讀取方法寫好了,那么我們需要另外一個方法來調度這個,因為我們這個 getLatestPrice
的網絡操作時同步的,所以我們的調度方法需要把它放到另外的線程中,我們使用 GCD
進行這個處理:
func reloadPrice() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
let price = self.getLatestPrice()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("reloadPrice"), userInfo: nil, repeats: false)
if let p = price {
var nsPrice = p as NSString
nsPrice = nsPrice.stringByReplacingOccurrencesOfString(",", withString: "")
let doublePrice = nsPrice.doubleValue
let differPrice = doublePrice - self.lastPrice
self.lastPrice = doublePrice;
self.priceLabel.text = NSString(format: "¥ %.2f", doublePrice) as? String
if differPrice > 0 {
self.differLabel.textColor = UIColor.redColor()
self.priceLabel.textColor = UIColor.redColor()
self.differLabel.text = NSString(format: "+%.2f", differPrice) as? String
}else{
self.differLabel.text = NSString(format: "%.2f", differPrice) as? String
self.differLabel.textColor = UIColor.greenColor()
self.priceLabel.textColor = UIColor.greenColor()
}
}
})
});
}
我們這里首先使用 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),...)
來調度異步線程,在這個線程中,我們調用了 getLatestPrice()
方法來獲取當前的比特幣價格,讀取成功后,我們要用這個數據來更新 UI 顯示了。而 UI 的操作時不能在異步線程中進行的。所以我們隨后又調用了 dispatch_async(dispatch_get_main_queue(),...)
方法將處理調度到主線程中。
由于服務端返回的數據格式是字符串類型的諸如這樣的價格數據
1,273.203
所以我們還需要對這個數據進行一下轉換:
var nsPrice = p as NSString
nsPrice = nsPrice.stringByReplacingOccurrencesOfString(",", withString: "")
let doublePrice = nsPrice.doubleValue
首先我們將字符串中的 ,
字符清除掉,然后使用 NSString 的 doubleValue
將字符串轉換成 Double 類型。
接下來,我們用當前的價格減去上次讀取的價格,計算出差價,就可以顯示出相對于上次讀取數據的漲跌幅度了。計算完成后,我們就重新將當前的價格存入 self.lastPrice
中,以便于下次的計算。
let differPrice = doublePrice - self.lastPrice
self.lastPrice = doublePrice;
最后,我們計算出了這些數據,再將他們顯示的 UILabel 上面。
self.priceLabel.text = NSString(format: "¥ %.2f", doublePrice) as? String
if differPrice > 0 {
self.differLabel.textColor = UIColor.redColor()
self.priceLabel.textColor = UIColor.redColor()
self.differLabel.text = NSString(format: "+%.2f", differPrice) as? String
}else{
self.differLabel.text = NSString(format: "%.2f", differPrice) as? String
self.differLabel.textColor = UIColor.greenColor()
self.priceLabel.textColor = UIColor.greenColor()
}
我們首先將當前價格設置到 self.priceLabel
, 然后根據漲跌幅度是正數還是負數設置 self.differLabel
的文字,如果是正數要在前面放一個 +
號。同時我們根據漲跌幅設置文本的顏色,如果是漲就設置為紅色,如果是跌就設置為綠色。
最后還有一行代碼我們要注意:
NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("reloadPrice"), userInfo: nil, repeats: false)
我們用 NSTimer
又調度了一下這個方法,在 3 秒鐘之后,重新請求最新價格。這樣我們的價格就能每隔 3 秒刷新一次。
數據讀取方法弄好之后,我們就可以在 viewDidLoad()
里面調用它了
override func viewDidLoad() {
super.viewDidLoad()
reloadPrice()
}
接下來可以運行一下項目,我們就會看到報價比特幣的最新價格顯示在界面上了。然后還可以不停的刷新。
顯示歷史報價
最新報價的現實邏輯我們實現完了,我們還可以做更多的事情,仔細研究 coindesk
的數據,我們發現還有一個接口可以實現查詢比特幣的報價歷史:
http://api.coindesk.com/v1/bpi/historical/close.json?start=2015-07-15&end=2015-07-24¤cy=CNY
訪問這個接口我們就可以看到諸如這樣的數據返回:
{
"bpi": {
"2015-07-15": 1756.5732,
"2015-07-16": 1719.6188,
"2015-07-17": 1723.7974,
"2015-07-18": 1698.9991,
"2015-07-19": 1686.3934,
"2015-07-20": 1723.3102,
"2015-07-21": 1702.5693,
"2015-07-22": 1710.3503
},
"disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index. BPI value data returned as CNY.",
"time": {
"updated": "Jul 23, 2015 09:53:17 UTC",
"updatedISO": "2015-07-23T09:53:17+00:00"
}
}
我們看到,這個接口返回了從起始日期到結束日期的比特幣價格信息,我們可以使用這個數據來顯示歷史數據,比如從當天往前 5 天之內的歷史數據。
那么我們先寫一個網絡讀取和解析數據的方法:
func getLastFiveDayPrice() -> Array<(String,String)> {
var curDate = NSDate()
var calendar = NSCalendar.currentCalendar()
let startDate = calendar.dateByAddingUnit(NSCalendarUnit.CalendarUnitDay, value: -6, toDate: curDate, options: nil)
let endDate = calendar.dateByAddingUnit(NSCalendarUnit.CalendarUnitDay, value: -1, toDate: curDate, options: nil)
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let url = "http://api.coindesk.com/v1/bpi/historical/close.json?start=\\\\(formatter.stringFromDate(startDate!))&end=\\\\(formatter.stringFromDate(endDate!))¤cy=CNY"
var result = Array<(String,String)>()
if let jsonData = NSData(contentsOfURL: NSURL(string: url)!) {
let json = JSON(data: jsonData)
let bpiDict:JSON = json["bpi"]
for (key,val) in bpiDict {
result.append((key,val.stringValue))
}
}
return result
}
這個方法會返回一個數組,我們仔細看一下這個數組的定義 Array<(String,String)>
,數組中的類型是 (String,String)
, 這種類型定義叫做 元組(Tuple) 是 Swift中的一個語言特性,關于元組,簡而言之就是一個包含了多個元素的類型,比如我們這里的元組包含了兩個 String
類型的值。
下面展示了元組類型的簡單用法:
let tuple = ("2012-2-21","1,232.23")
//可以通過索引來引用元組的元素
print("\\\\(tuple.0) price is \\\\(tuple.1)")
//還可以為元組的項制定名稱
let (date,price) = tuple
print("\\\\(date) price is \\\\(price)")
我們看到,我們可以通過索引的方式,也可以通過為元組項指定名稱的方式來引用元組中的值。這里簡單介紹一下元組的概念,更詳細的內容大家可以參考相關資料。
接下來,我們看一下這個方法的內容,首先我們通過格式化 NSDate
輸出的方式拼接出 URL,這里我們用到了 NSCalendar
,這個類可以通過 dateByAddingUnit
方法操作 NSDate
的各個日期屬性,比如將當前的日期減去多少天,我們用這個方法得到當前日期往前 5 天和 1 天的日期值,用于得到這個期間的比特幣價格。
我們還用到了 NSDateFormatter
,這個類可以將 NSDate
的值進行格式化輸出,得到我們需要的日期輸出格式。我們這里需要類似 2012-03-12
的這種日期格式,所以我們將日期格式定義為 yyyy-MM-dd
。
最后通過 NSDateFormatter
的 stringFromDate
方法輸出格式化后的日期值:
var curDate = NSDate()
var calendar = NSCalendar.currentCalendar()
let startDate = calendar.dateByAddingUnit(NSCalendarUnit.CalendarUnitDay, value: -6, toDate: curDate, options: nil)
let endDate = calendar.dateByAddingUnit(NSCalendarUnit.CalendarUnitDay, value: -1, toDate: curDate, options: nil)
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let url = "http://api.coindesk.com/v1/bpi/historical/close.json?start=\\\\(formatter.stringFromDate(startDate!))&end=\\\\(formatter.stringFromDate(endDate!))¤cy=CNY"
拼接好 URL 之后,我們就可以開始請求數據了,看一看下面的代碼:
var result = Array<(String,String)>()
if let jsonData = NSData(contentsOfURL: NSURL(string: url)!) {
let json = JSON(data: jsonData)
let bpiDict:JSON = json["bpi"]
for (key,val) in bpiDict {
result.append((key,val.stringValue))
}
}
首先我們定義了一個 result
數組,用于返回我們的價格列表。然后我們使用 NSData
的構造方法來請求接口的數據。請求到數據后,我們使用 SwiftyJSON
的 JSON
類進行解析,隨后的 for
循環中,我們遍歷了 bpi
節點中的所有的鍵值,將這些鍵值通過元組的方式添加到 result
列表中。
result.append((key,val.stringValue))
注意條語句,我們構造元組的方式 (key,val.stringValue)
, 因為我們的元組定義為 (String,String)
類型,在 for
循環中,我們的 key
變量是 String
類型的,所以我們可以直接用這個值來構建元組的第一項,而 val
不是 String
類型的。我們必須使用 SwiftyJSON
中的 stringValue
方法取得這個節點的 String
類型的值來構建元組的第二項。
到此為止我們的歷史數據讀取方法也完成了。
構造歷史價格界面
數據讀取方法構造完成后,我們就可以開始處理 UI 界面了,我們創建了 buildHistoryLabels
方法:
func buildHistoryLabels(priceList: Array<(String,String)>) {
var count = 0.0
var labelTitle = UILabel(frame: CGRectMake(CGFloat(30.0), CGFloat(220.0), CGFloat(200.0), CGFloat(30.0)))
labelTitle.text = "歷史價格"
self.view.addSubview(labelTitle)
for (date, price) in priceList {
var labelHistory = UILabel(frame: CGRectMake(CGFloat(30.0), CGFloat(250 + count * 40.0), CGFloat(200.0), CGFloat(30.0)))
labelHistory.text = "\\\\(date) \\\\(price)"
self.view.addSubview(labelHistory)
count++
}
}
這個方法接受一個數組作為參數,這個數組的內容就是我們的價格列表。首先我們這里構建了這組 UILabel 的標題:
var labelTitle = UILabel(frame: CGRectMake(CGFloat(30.0), CGFloat(220.0), CGFloat(200.0), CGFloat(30.0)))
labelTitle.text = "歷史價格"
self.view.addSubview(labelTitle)
然后我們通過一個 for
循環來遍歷價格列表,取出元組的兩項內容,分別以 date
和 price
來命名,并用這些數據構建出 UILabel
并添加到 UI 視圖中:
for (date, price) in priceList {
var labelHistory = UILabel(frame: CGRectMake(CGFloat(30.0), CGFloat(250 + count * 40.0), CGFloat(200.0), CGFloat(30.0)))
labelHistory.text = "\\\\(date) \\\\(price)"
self.view.addSubview(labelHistory)
count++
}
現在我們可以運行 APP 了,我們看到當前的價格,以及近期的價格都展示在了界面中:

到此為止,我們利用 SwiftyJSON
完成的讀取了 JSON 數據。我們的比特幣查詢 APP 也基本完成了。當然這個示例 APP 還有很多不完善的地方,如果大家有興趣,讓他變的更加完善。