Swift 中处理 JSON 数据有不少种方式,可使用原生的 NSJSONSerialization,也可使用不少第三方库。原生的 NSJSONSerialization 方式这篇文章中介绍过。此次咱们介绍一个第三方库 SwiftyJSON
而且用它来制做一个有趣的 APP.git
首先,咱们来了解一下什么是 SwiftyJSON
, 而且咱们为何要用这个库。好比咱们要解析这个比特币实时价格的接口:github
这个接口的数据格式以下:swift
{ "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
方式,获得比特币的人民币价格的话,咱们写出的代码大概就是这样的:api
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 主页:app
https://github.com/SwiftyJSON/SwiftyJSON异步
下面咱们就以一个例子来继续了解 SwiftyJSON。async
咱们今天要作的是一个比特币实时价格的 APP,这里咱们会用到 SwiftyJSON 来解析服务端的数据。
首先咱们建立一个项目, Single View Application
类型:
而后设置好项目的基本信息:
而后就是要引入 SwiftyJSON
库,
另外还能够下载咱们预配置好的项目来进行开发:bitprice-start.zip
如今咱们就进入主题吧,首先咱们开始构建 UI 界面,打开 Main.storyboard
进行编辑。
storyboard
中拖入三个 UILabel
[构建 storyboard 界面]((http://www.swiftcafe.io/images/swifty-json/3.png)
其中第一个 Label 的 text
属性设置为 "当前价格", 后两个 Label 的 text
设置为空,用做显示比特币的价格。
UILabel
连接到主控制器的 Outlet
中,在打开 storyboard 视图的同时,按住 Option
并点击 ViewController.swift
。这样编辑界面上同时显示了 storyboard
和控制器的代码,而后咱们在 storyboard
中选中 Label,而后按住 control
拖动到控制器的代码中:[创建连接]((http://www.swiftcafe.io/images/swifty-json/4.jpg)
随后会弹出一个变量名称提示框,咱们将第一个 UILabel 命名为 priceLabel
,将第二个 UILabel 命名为 differLabel
。
[变量命名]((http://www.swiftcafe.io/images/swifty-json/5.jpg)
最后,咱们在给 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 了,咱们看到当前的价格,以及近期的价格都展现在了界面中:
[价格列表]((http://www.swiftcafe.io/images/swifty-json/7.png)
到此为止,咱们利用 SwiftyJSON
完成的读取了 JSON 数据。咱们的比特币查询 APP 也基本完成了。固然这个示例 APP 还有不少不完善的地方,若是你们有兴趣,让他变的更加完善。