Swift 中使用 SwiftyJSON 制作一个比特币价格 APP



- 作者: SwiftCafe


Swift 中处理 JSON 数据有很多种方式,可以使用原生的 NSJSONSerialization,也可以使用很多第三方库。原生的 NSJSONSerialization 方式这篇文章中介绍过。这次我们介绍一个第三方库 SwiftyJSON 并且用它来制作一个有趣的 APP.

关于 SwiftyJSON

首先,我们来了解一下什么是 SwiftyJSON, 并且我们为什么要用这个库。比如我们要解析这个比特币实时价格的接口:

http://api.coindesk.com/v1/bpi/currentprice/CNY.json

这个接口的数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"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 方式,得到比特币的人民币价格的话,我们写出的代码大概就是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 来达到同样的目的要写的代码:

1
2
3
4
5
6
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 进行编辑。

  1. 首先,我们在 storyboard 中拖入三个 UILabel

构建 storyboard 界面

其中第一个 Label 的 text 属性设置为 “当前价格”, 后两个 Label 的 text 设置为空,用作显示比特币的价格。

  1. 然后,我们将两个用于显示价格的 UILabel 链接到主控制器的 Outlet 中,在打开 storyboard 视图的同时,按住 Option 并点击 ViewController.swift。这样编辑界面上同时显示了 storyboard 和控制器的代码,然后我们在 storyboard 中选中 Label,然后按住 control 拖动到控制器的代码中:

建立链接

随后会弹出一个变量名称提示框,我们将第一个 UILabel 命名为 priceLabel,将第二个 UILabel 命名为 differLabel

变量命名

最后,我们在给 ViewController 建立一个新的属性 lastPrice, 存储上次更新的价格,用于计算当前价格相对于上次的涨跌幅。

这样我们的 ViewController 的属性定义如下:

1
2
3
4
5
6
7
8
9
class ViewController: UIViewController {
@IBOutlet var priceLabel: UILabel!
@IBOutlet var differLabel: UILabel!
var lastPrice:Double = 0.0
}

两个 IBOutlet 链接的 UILabel, 还有一个 Double 变量用于存放上次的价格。

基础结构设置好后,我们就可以开始构建应用的逻辑了,我们首先定义一个方法 getLatestPrice(),用于获取比特币最新的价格:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 数据

1
2
let json = JSON(data: jsonData)
return json["bpi"]["CNY"]["rate"].stringValue

只有两行代码,就完成了数据的提取,很方便吧。

数据读取方法写好了,那么我们需要另外一个方法来调度这个,因为我们这个 getLatestPrice 的网络操作时同步的,所以我们的调度方法需要把它放到另外的线程中,我们使用 GCD 进行这个处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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

所以我们还需要对这个数据进行一下转换:

1
2
3
var nsPrice = p as NSString
nsPrice = nsPrice.stringByReplacingOccurrencesOfString(",", withString: "")
let doublePrice = nsPrice.doubleValue

首先我们将字符串中的 , 字符清除掉,然后使用 NSString 的 doubleValue 将字符串转换成 Double 类型。

接下来,我们用当前的价格减去上次读取的价格,计算出差价,就可以显示出相对于上次读取数据的涨跌幅度了。计算完成后,我们就重新将当前的价格存入 self.lastPrice 中,以便于下次的计算。

1
2
let differPrice = doublePrice - self.lastPrice
self.lastPrice = doublePrice;

最后,我们计算出了这些数据,再将他们显示的 UILabel 上面。

1
2
3
4
5
6
7
8
9
10
11
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 的文字,如果是正数要在前面放一个 + 号。同时我们根据涨跌幅设置文本的颜色,如果是涨就设置为红色,如果是跌就设置为绿色。

最后还有一行代码我们要注意:

1
NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("reloadPrice"), userInfo: nil, repeats: false)

我们用 NSTimer 又调度了一下这个方法,在 3 秒钟之后,重新请求最新价格。这样我们的价格就能每隔 3 秒刷新一次。

数据读取方法弄好之后,我们就可以在 viewDidLoad() 里面调用它了

1
2
3
4
5
6
override func viewDidLoad() {
super.viewDidLoad()
reloadPrice()
}

接下来可以运行一下项目,我们就会看到报价比特币的最新价格显示在界面上了。然后还可以不停的刷新。

显示历史报价

最新报价的现实逻辑我们实现完了,我们还可以做更多的事情,仔细研究 coindesk 的数据,我们发现还有一个接口可以实现查询比特币的报价历史:

http://api.coindesk.com/v1/bpi/historical/close.json?start=2015-07-15&end=2015-07-24&currency=CNY

访问这个接口我们就可以看到诸如这样的数据返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"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 天之内的历史数据。

那么我们先写一个网络读取和解析数据的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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!))&currency=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 类型的值。

下面展示了元组类型的简单用法:

1
2
3
4
5
6
7
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

最后通过 NSDateFormatterstringFromDate 方法输出格式化后的日期值:

1
2
3
4
5
6
7
8
9
10
11
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!))&currency=CNY"

拼接好 URL 之后,我们就可以开始请求数据了,看一看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 的构造方法来请求接口的数据。请求到数据后,我们使用 SwiftyJSONJSON 类进行解析,随后的 for 循环中,我们遍历了 bpi 节点中的所有的键值,将这些键值通过元组的方式添加到 result 列表中。

1
result.append((key,val.stringValue))

注意条语句,我们构造元组的方式 (key,val.stringValue), 因为我们的元组定义为 (String,String) 类型,在 for 循环中,我们的 key 变量是 String 类型的,所以我们可以直接用这个值来构建元组的第一项,而 val 不是 String 类型的。我们必须使用 SwiftyJSON 中的 stringValue 方法取得这个节点的 String 类型的值来构建元组的第二项。

到此为止我们的历史数据读取方法也完成了。

构造历史价格界面

数据读取方法构造完成后,我们就可以开始处理 UI 界面了,我们创建了 buildHistoryLabels 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 的标题:

1
2
3
var labelTitle = UILabel(frame: CGRectMake(CGFloat(30.0), CGFloat(220.0), CGFloat(200.0), CGFloat(30.0)))
labelTitle.text = "历史价格"
self.view.addSubview(labelTitle)

然后我们通过一个 for 循环来遍历价格列表,取出元组的两项内容,分别以 dateprice 来命名,并用这些数据构建出 UILabel 并添加到 UI 视图中:

1
2
3
4
5
6
7
8
9
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 还有很多不完善的地方,如果大家有兴趣,让他变的更加完善。

如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~

本站文章均为原创内容,如需转载请注明出处,谢谢。




微信公众平台
更多精彩内容,请关注微信公众号


公众号:swift-cafe
邮件订阅
请输入您的邮箱,我们会把最新的内容推送给您: