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

时间:2022-11-18 20:40:56

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

关于 SwiftyJSON

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

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

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

{
"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 数据的处理中。怎么样,挺好用的把。

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

关于 SwifyJSON 的更多介绍,大家还可以参看它的 Github 主页:

https://github.com/SwiftyJSON/SwiftyJSON

下面我们就以一个例子来继续了解 SwiftyJSON。

比特币查询应用

我们今天要做的是一个比特币实时价格的 APP,这里我们会用到 SwiftyJSON 来解析服务端的数据。

首先我们创建一个项目, Single View Application 类型:

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

然后设置好项目的基本信息:

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

然后就是要引入 SwiftyJSON 库,

另外还可以下载我们预配置好的项目来进行开发:bitprice-start.zip

现在我们就进入主题吧,首先我们开始构建 UI 界面,打开 Main.storyboard 进行编辑。

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

![构建 storyboard 界面]((http://www.swiftcafe.io/images/swifty-json/3.png)

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

  1. 然后,我们将两个用于显示价格的 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&currency=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!))&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 类型的值。

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

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 方法输出格式化后的日期值:

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 之后,我们就可以开始请求数据了,看一看下面的代码:

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 列表中。

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 循环来遍历价格列表,取出元组的两项内容,分别以 dateprice 来命名,并用这些数据构建出 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 还有很多不完善的地方,如果大家有兴趣,让他变的更加完善。