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閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容

  • 簡評:敲黑板!考試要考的比特幣知識點,都是送分題。 本文是比特幣官方 FAQ,僅做科普。 目錄概覽 什么是比特幣?...
    極小光閱讀 5,975評論 5 89
  • 邯鄲的男孩子啊 高高瘦瘦的 喜歡燙頭 喜歡改校服 大長腿比姑娘細 喜歡冬天露出腳踝 踩在小電車的踏板上 帶著后座的...
    吃了口鹽閱讀 220評論 0 0
  • 如果不是樊登讀書會,我想這本書可能我這輩子都不會去念,因為標題吸引不了我,可能在我年紀更大的時候才會有興趣去追根溯...
    上海九叔閱讀 323評論 0 0
  • 唯心順道悟參中, 舞榭歌臺萬世空。 且看蒼松寒峭處, 浮華褪盡亦從容。 中國的文字道義顯然,暗蘊佛法! 悟,左邊一...
    凌峰峰行閱讀 370評論 5 3
  • 這應該是我今年第二次打羽毛球,不同的是,今天有教練教學,打了僅僅15分鐘,就已經全身發熱,教練讓我觀察旁邊一個場地...
    Queeny028閱讀 219評論 0 0