Swift 中處理 JSON 數(shù)據(jù)有很多種方式踏施,可以使用原生的 NSJSONSerialization麸恍,也可以使用很多第三方庫索守。原生的 NSJSONSerialization 方式這篇文章中介紹過瞳购。這次我們介紹一個(gè)第三方庫 SwiftyJSON
并且用它來制作一個(gè)有趣的 APP.
關(guān)于 SwiftyJSON
首先褪尝,我們來了解一下什么是 SwiftyJSON
, 并且我們?yōu)槭裁匆眠@個(gè)庫因谎。比如我們要解析這個(gè)比特幣實(shí)時(shí)價(jià)格的接口:
這個(gè)接口的數(shù)據(jù)格式如下:
{
"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
方式基括,得到比特幣的人民幣價(jià)格的話,我們寫出的代碼大概就是這樣的:
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"]!)
}
}
}
}
那么我們?cè)賮砜匆幌虏撇恚覀冇?SwiftyJSON 來達(dá)到同樣的目的要寫的代碼:
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"])
}
是不是感覺精簡了很多呢风皿,對(duì)匠璧,就是這個(gè)效果桐款。SwiftyJSON
的以大好處就是,不用你來處理 Swift 中的類型轉(zhuǎn)換夷恍,它會(huì)自動(dòng)幫你處理類型等開發(fā)語言相關(guān)的問題魔眨,讓你專注于 JSON 數(shù)據(jù)的處理中。怎么樣,挺好用的把遏暴。
![](http://www.swiftcafe.io/images/art/e_smile.png)
關(guān)于 SwifyJSON 的更多介紹侄刽,大家還可以參看它的 Github 主頁:
https://github.com/SwiftyJSON/SwiftyJSON
下面我們就以一個(gè)例子來繼續(xù)了解 SwiftyJSON。
比特幣查詢應(yīng)用
我們今天要做的是一個(gè)比特幣實(shí)時(shí)價(jià)格的 APP朋凉,這里我們會(huì)用到 SwiftyJSON 來解析服務(wù)端的數(shù)據(jù)州丹。
首先我們創(chuàng)建一個(gè)項(xiàng)目, Single View Application
類型:
![](http://www.swiftcafe.io/images/swifty-json/1.png)
然后設(shè)置好項(xiàng)目的基本信息:
![](http://www.swiftcafe.io/images/swifty-json/2.png)
然后就是要引入 SwiftyJSON
庫,
另外還可以下載我們預(yù)配置好的項(xiàng)目來進(jìn)行開發(fā):bitprice-start.zip
現(xiàn)在我們就進(jìn)入主題吧杂彭,首先我們開始構(gòu)建 UI 界面墓毒,打開 Main.storyboard
進(jìn)行編輯。
- 首先亲怠,我們?cè)?
storyboard
中拖入三個(gè)UILabel
![]((http://www.swiftcafe.io/images/swifty-json/3.png)
其中第一個(gè) Label 的 text
屬性設(shè)置為 "當(dāng)前價(jià)格"蚁鳖, 后兩個(gè) Label 的 text
設(shè)置為空,用作顯示比特幣的價(jià)格赁炎。
- 然后醉箕,我們將兩個(gè)用于顯示價(jià)格的
UILabel
鏈接到主控制器的Outlet
中,在打開 storyboard 視圖的同時(shí)徙垫,按住Option
并點(diǎn)擊ViewController.swift
讥裤。這樣編輯界面上同時(shí)顯示了storyboard
和控制器的代碼,然后我們?cè)?storyboard
中選中 Label姻报,然后按住control
拖動(dòng)到控制器的代碼中:
![]((http://www.swiftcafe.io/images/swifty-json/4.jpg)
隨后會(huì)彈出一個(gè)變量名稱提示框己英,我們將第一個(gè) UILabel 命名為 priceLabel
,將第二個(gè) UILabel 命名為 differLabel
吴旋。
![]((http://www.swiftcafe.io/images/swifty-json/5.jpg)
最后损肛,我們?cè)诮o ViewController
建立一個(gè)新的屬性 lastPrice
, 存儲(chǔ)上次更新的價(jià)格荣瑟,用于計(jì)算當(dāng)前價(jià)格相對(duì)于上次的漲跌幅治拿。
這樣我們的 ViewController
的屬性定義如下:
class ViewController: UIViewController {
@IBOutlet var priceLabel: UILabel!
@IBOutlet var differLabel: UILabel!
var lastPrice:Double = 0.0
}
兩個(gè) IBOutlet
鏈接的 UILabel
, 還有一個(gè) Double
變量用于存放上次的價(jià)格笆焰。
基礎(chǔ)結(jié)構(gòu)設(shè)置好后劫谅,我們就可以開始構(gòu)建應(yīng)用的邏輯了,我們首先定義一個(gè)方法 getLatestPrice()
嚷掠,用于獲取比特幣最新的價(jià)格:
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
的構(gòu)造方法從指定的 URL 地址讀取了比特幣價(jià)格數(shù)據(jù)捏检,然后用到了 SwiftyJSON
來讀取和解析返回的 JSON
數(shù)據(jù)
let json = JSON(data: jsonData)
return json["bpi"]["CNY"]["rate"].stringValue
只有兩行代碼,就完成了數(shù)據(jù)的提取不皆,很方便吧贯城。
數(shù)據(jù)讀取方法寫好了,那么我們需要另外一個(gè)方法來調(diào)度這個(gè)霹娄,因?yàn)槲覀冞@個(gè) getLatestPrice
的網(wǎng)絡(luò)操作時(shí)同步的能犯,所以我們的調(diào)度方法需要把它放到另外的線程中鲫骗,我們使用 GCD
進(jìn)行這個(gè)處理:
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),...)
來調(diào)度異步線程,在這個(gè)線程中悲雳,我們調(diào)用了 getLatestPrice()
方法來獲取當(dāng)前的比特幣價(jià)格挎峦,讀取成功后香追,我們要用這個(gè)數(shù)據(jù)來更新 UI 顯示了合瓢。而 UI 的操作時(shí)不能在異步線程中進(jìn)行的。所以我們隨后又調(diào)用了 dispatch_async(dispatch_get_main_queue(),...)
方法將處理調(diào)度到主線程中透典。
由于服務(wù)端返回的數(shù)據(jù)格式是字符串類型的諸如這樣的價(jià)格數(shù)據(jù)
1,273.203
所以我們還需要對(duì)這個(gè)數(shù)據(jù)進(jìn)行一下轉(zhuǎn)換:
var nsPrice = p as NSString
nsPrice = nsPrice.stringByReplacingOccurrencesOfString(",", withString: "")
let doublePrice = nsPrice.doubleValue
首先我們將字符串中的 ,
字符清除掉晴楔,然后使用 NSString 的 doubleValue
將字符串轉(zhuǎn)換成 Double 類型。
接下來峭咒,我們用當(dāng)前的價(jià)格減去上次讀取的價(jià)格税弃,計(jì)算出差價(jià),就可以顯示出相對(duì)于上次讀取數(shù)據(jù)的漲跌幅度了凑队。計(jì)算完成后则果,我們就重新將當(dāng)前的價(jià)格存入 self.lastPrice
中,以便于下次的計(jì)算漩氨。
let differPrice = doublePrice - self.lastPrice
self.lastPrice = doublePrice;
最后西壮,我們計(jì)算出了這些數(shù)據(jù),再將他們顯示的 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()
}
我們首先將當(dāng)前價(jià)格設(shè)置到 self.priceLabel
, 然后根據(jù)漲跌幅度是正數(shù)還是負(fù)數(shù)設(shè)置 self.differLabel
的文字款青,如果是正數(shù)要在前面放一個(gè) +
號(hào)。同時(shí)我們根據(jù)漲跌幅設(shè)置文本的顏色霍狰,如果是漲就設(shè)置為紅色抡草,如果是跌就設(shè)置為綠色。
最后還有一行代碼我們要注意:
NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("reloadPrice"), userInfo: nil, repeats: false)
我們用 NSTimer
又調(diào)度了一下這個(gè)方法蔗坯,在 3 秒鐘之后康震,重新請(qǐng)求最新價(jià)格。這樣我們的價(jià)格就能每隔 3 秒刷新一次宾濒。
數(shù)據(jù)讀取方法弄好之后签杈,我們就可以在 viewDidLoad()
里面調(diào)用它了
override func viewDidLoad() {
super.viewDidLoad()
reloadPrice()
}
接下來可以運(yùn)行一下項(xiàng)目,我們就會(huì)看到報(bào)價(jià)比特幣的最新價(jià)格顯示在界面上了鼎兽。然后還可以不停的刷新答姥。
顯示歷史報(bào)價(jià)
最新報(bào)價(jià)的現(xiàn)實(shí)邏輯我們實(shí)現(xiàn)完了,我們還可以做更多的事情谚咬,仔細(xì)研究 coindesk
的數(shù)據(jù)鹦付,我們發(fā)現(xiàn)還有一個(gè)接口可以實(shí)現(xiàn)查詢比特幣的報(bào)價(jià)歷史:
http://api.coindesk.com/v1/bpi/historical/close.json?start=2015-07-15&end=2015-07-24¤cy=CNY
訪問這個(gè)接口我們就可以看到諸如這樣的數(shù)據(jù)返回:
{
"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"
}
}
我們看到,這個(gè)接口返回了從起始日期到結(jié)束日期的比特幣價(jià)格信息择卦,我們可以使用這個(gè)數(shù)據(jù)來顯示歷史數(shù)據(jù)敲长,比如從當(dāng)天往前 5 天之內(nèi)的歷史數(shù)據(jù)郎嫁。
那么我們先寫一個(gè)網(wǎng)絡(luò)讀取和解析數(shù)據(jù)的方法:
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
}
這個(gè)方法會(huì)返回一個(gè)數(shù)組,我們仔細(xì)看一下這個(gè)數(shù)組的定義 Array<(String,String)>
祈噪,數(shù)組中的類型是 (String,String)
泽铛, 這種類型定義叫做 元組(Tuple) 是 Swift中的一個(gè)語言特性,關(guān)于元組辑鲤,簡而言之就是一個(gè)包含了多個(gè)元素的類型盔腔,比如我們這里的元組包含了兩個(gè) String
類型的值。
下面展示了元組類型的簡單用法:
let tuple = ("2012-2-21","1,232.23")
//可以通過索引來引用元組的元素
print("\\\\(tuple.0) price is \\\\(tuple.1)")
//還可以為元組的項(xiàng)制定名稱
let (date,price) = tuple
print("\\\\(date) price is \\\\(price)")
我們看到月褥,我們可以通過索引的方式弛随,也可以通過為元組項(xiàng)指定名稱的方式來引用元組中的值。這里簡單介紹一下元組的概念宁赤,更詳細(xì)的內(nèi)容大家可以參考相關(guān)資料舀透。
接下來,我們看一下這個(gè)方法的內(nèi)容决左,首先我們通過格式化 NSDate
輸出的方式拼接出 URL愕够,這里我們用到了 NSCalendar
,這個(gè)類可以通過 dateByAddingUnit
方法操作 NSDate
的各個(gè)日期屬性佛猛,比如將當(dāng)前的日期減去多少天惑芭,我們用這個(gè)方法得到當(dāng)前日期往前 5 天和 1 天的日期值,用于得到這個(gè)期間的比特幣價(jià)格挚躯。
我們還用到了 NSDateFormatter
强衡,這個(gè)類可以將 NSDate
的值進(jìn)行格式化輸出,得到我們需要的日期輸出格式码荔。我們這里需要類似 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 之后缩搅,我們就可以開始請(qǐng)求數(shù)據(jù)了越败,看一看下面的代碼:
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))
}
}
首先我們定義了一個(gè) result
數(shù)組,用于返回我們的價(jià)格列表硼瓣。然后我們使用 NSData
的構(gòu)造方法來請(qǐng)求接口的數(shù)據(jù)究飞。請(qǐng)求到數(shù)據(jù)后,我們使用 SwiftyJSON
的 JSON
類進(jìn)行解析堂鲤,隨后的 for
循環(huán)中亿傅,我們遍歷了 bpi
節(jié)點(diǎn)中的所有的鍵值,將這些鍵值通過元組的方式添加到 result
列表中瘟栖。
result.append((key,val.stringValue))
注意條語句葵擎,我們構(gòu)造元組的方式 (key,val.stringValue)
, 因?yàn)槲覀兊脑M定義為 (String,String)
類型,在 for
循環(huán)中半哟,我們的 key
變量是 String
類型的酬滤,所以我們可以直接用這個(gè)值來構(gòu)建元組的第一項(xiàng)签餐,而 val
不是 String
類型的。我們必須使用 SwiftyJSON
中的 stringValue
方法取得這個(gè)節(jié)點(diǎn)的 String
類型的值來構(gòu)建元組的第二項(xiàng)盯串。
到此為止我們的歷史數(shù)據(jù)讀取方法也完成了氯檐。
構(gòu)造歷史價(jià)格界面
數(shù)據(jù)讀取方法構(gòu)造完成后,我們就可以開始處理 UI 界面了体捏,我們創(chuàng)建了 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 = "歷史價(jià)格"
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++
}
}
這個(gè)方法接受一個(gè)數(shù)組作為參數(shù)冠摄,這個(gè)數(shù)組的內(nèi)容就是我們的價(jià)格列表。首先我們這里構(gòu)建了這組 UILabel 的標(biāo)題:
var labelTitle = UILabel(frame: CGRectMake(CGFloat(30.0), CGFloat(220.0), CGFloat(200.0), CGFloat(30.0)))
labelTitle.text = "歷史價(jià)格"
self.view.addSubview(labelTitle)
然后我們通過一個(gè) for
循環(huán)來遍歷價(jià)格列表译打,取出元組的兩項(xiàng)內(nèi)容耗拓,分別以 date
和 price
來命名拇颅,并用這些數(shù)據(jù)構(gòu)建出 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++
}
現(xiàn)在我們可以運(yùn)行 APP 了奏司,我們看到當(dāng)前的價(jià)格,以及近期的價(jià)格都展示在了界面中:
![](http://www.swiftcafe.io/images/swifty-json/7.png)
到此為止樟插,我們利用 SwiftyJSON
完成的讀取了 JSON 數(shù)據(jù)韵洋。我們的比特幣查詢 APP 也基本完成了。當(dāng)然這個(gè)示例 APP 還有很多不完善的地方黄锤,如果大家有興趣搪缨,讓他變的更加完善。