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

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

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

https://github.com/SwiftyJSON/SwiftyJSON

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

比特币查询应用

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

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

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

然后就是要引入 SwiftyJSON 库,

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

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

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

![]((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 了,我们看到当前的价格,以及近期的价格都展示在了界面中:

到此为止,我们利用 SwiftyJSON 完成的读取了 JSON 数据。我们的比特币查询 APP 也基本完成了。当然这个示例 APP 还有很多不完善的地方,如果大家有兴趣,让他变的更加完善。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容

  • 简评:敲黑板!考试要考的比特币知识点,都是送分题。 本文是比特币官方 FAQ,仅做科普。 目录概览 什么是比特币?...
    极小光阅读 5,938评论 5 89
  • 邯郸的男孩子啊 高高瘦瘦的 喜欢烫头 喜欢改校服 大长腿比姑娘细 喜欢冬天露出脚踝 踩在小电车的踏板上 带着后座的...
    吃了口盐阅读 212评论 0 0
  • 如果不是樊登读书会,我想这本书可能我这辈子都不会去念,因为标题吸引不了我,可能在我年纪更大的时候才会有兴趣去追根溯...
    上海九叔阅读 308评论 0 0
  • 唯心顺道悟参中, 舞榭歌台万世空。 且看苍松寒峭处, 浮华褪尽亦从容。 中国的文字道义显然,暗蕴佛法! 悟,左边一...
    凌峰峰行阅读 359评论 5 3
  • 这应该是我今年第二次打羽毛球,不同的是,今天有教练教学,打了仅仅15分钟,就已经全身发热,教练让我观察旁边一个场地...
    Queeny028阅读 208评论 0 0