iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 5
0
自我挑戰組

一天一蘋果,Bug 遠離我。系列 第 5

Day 05: 折扣計算 && 你的資料不是你的資料!?

今天你從 API 中獲取的資料真的是正確的,也是你需要的嗎?

前言:

https://ithelp.ithome.com.tw/upload/images/20181020/20107701hRigqqjBoA.png

其實這篇文章踩雷後新增的文章,所以也是臨時起意的 XD

前兩次篇文章的教學我們學會如何使用原生或是第三方程式庫獲取網路上的數據,並且將獲取到的 JSON 檔解析成我們所需要的資料型態。這次教學我們會作 書籍折扣的計算 以及 檢查串接到的資料是否真的都是正確的?都是我們需要的嗎?這篇文章將以及串接下來的資料該怎麼處理。

#計算折扣 & Debug

( 內有踩雷過程,小心服用 )
因為事前做功課的時候發現,蠻多有關書籍的網站或是 APP 都有顯示「 折扣的 % 數 」(e.g.-75%)。所以這邊我們也來計算它的 % 數。然後,我們希望這個折扣可以放到我們書籍的 Model 中,而不是等到顯示到 UI 上的時候才計算,所以最好的方式應該就是在初始化的時候給賦給它一個值,讓我們來完成它吧。
我們在 Book 新增一個叫做 discount 的計算屬性:

var discount: Int? {
    if originPrice != 0 && sellPrice {
        // 我們希望這兩者都不為 0,若是其中一個不存在就返回 nil。
        let result = Int((Double(sellPrice) / Double(originPrice)) * 100.0)
        return result
    } else {
        return nil
    }
}

完成啦, 這時候我們來需要一個 for in 迴圈來 print 一下結果,這邊我們連帶印出它的原價及售價來檢查有沒有什麼 bug:

for book in books {
  print("原價:",book.originPrice, "售價:",book.sellPrice)
  // 若是 book.dicount == nil,則會印出無折扣。
  print("折扣:", book.discount ?? "無折扣")
}

/images/emoticon/emoticon04.gif 這時候我們發生了突發狀況! /images/emoticon/emoticon04.gif

https://ithelp.ithome.com.tw/upload/images/20181020/201077017o4d8b2Y6N.png
https://ithelp.ithome.com.tw/upload/images/20181020/20107701HD1nJINTw7.png

What the hell ???通常我們在接來自後端的資訊或是 API 的時候,我們往往不知道他回傳的東西是不是我們想要的,所以如果後端能夠與我們合作那當然是很棒的,我們可以溝通我們需要怎麼樣的資訊,但如果今天是個 OpenData 是一個開放式的資料 API,那…

就乖乖串起來吧… 願意給我們資料就要偷笑了 /images/emoticon/emoticon14.gif

我們上圖其中一個問題是,我們以為原價或是售價會只會有兩種狀況:

  • 售價原價都有,但售價原價不同。
  • 沒有原價,只有售價。( 也就是沒有打折 )

但是上面的情況很明顯的,出現了例外,因此我們需要再加上一行判斷來過濾這種售價跟原價相同的情況:

var discount: Int? {
// 我們希望這兩者都不為 0 時才能夠計算折扣,若是其中一個不存在就返回 nil。
    if originPrice != 0 && sellPrice != 0 && originPrice > sellPrice {
        let result = Int((Double(sellPrice) / Double(originPrice)) * 100.0)
        return result
    } else {
        return nil
    }
}

如此以來我們應該可以順利的將它過濾成無折扣的情況,那麼下面那個意外情況難道那本書的原價真的是一塊錢嗎?我們實際到 JSON 中看它的值:

https://ithelp.ithome.com.tw/upload/images/20181020/20107701swmMYJxFln.png

原來是原價是 1,200 啊!所以很明顯的,SwiftyJSON 在幫我們轉成數字的時候,跟我們使用 NSString 轉數字 value 一樣,只會取得整個字串前面屬於數字的部分,但如果出現了不是數字的欄位,則會中斷這個值的獲取。

// 舉個範例給大家看
// 我先 extension 我們的String,並新增一個回傳 Double 的方法。
extension String {
  func toDouble() -> Double {
    let double = (self as NSString).doubleValue
    return double
  }
}
let string1 = "12.34"
let string2 = "1,234.56"
let string3 = "1.234B56"
print(string1.toDouble()) // 12.34
print(string2.toDouble()) // 1.0
print(string3.toDouble()) // 1.234

所以在這邊我們只好忍痛把我們 Book 這個 Model 裡面的屬性都改成 stringValue,別忘了 ISBN ,並且額外寫一個方法來計算折扣了? 。

如此一來我們就能正確地得到我們的結果啦~
https://ithelp.ithome.com.tw/upload/images/20181020/20107701uiVJRjM9IH.png

??? 你以為這樣就結束了嗎?? ???

不,我們還有一件非常重要的事情要做,那就是去掉 books 陣列重複的書籍資料,也就是「陣列去重(ㄔㄨ ㄥ ˊ)」,即使他今天後端沒有重複資料的問題,我們應該也要預防這種情況,搞不好哪天心血來潮給你 100 個 一樣的書籍資料( 嘿嘿 ?

俗話說的好:「 預防勝於治療。」,事前就要防範好這些我們能夠過濾掉的東西,之後就算資料接收錯誤也會被我們過濾掉了。當然我們要怎麼去重複的書籍呢?這邊大家第一個想法可能都是 for 迴圈來比對是否有重複的選項,但是這樣太麻煩了,也太消耗效能了,我們當然也可以在 append 到 Array 前先過濾掉。
但這邊我們 Swift 中有一個叫做「 Set 」的集合型別,然而 Set 的特性是:「 用來儲存相同型別且沒有順序、沒有重複的值 」,所以我們這邊會先將我們的 Array 轉成 Set 去掉重複的值,最後再將它轉化成 Array。

let deduplicationBooks = Array(Set(books))
print("原有資料數目:\(books.count)")
print("去重資料書目:\(deduplicationBooks.count)")

但是首先我們要先將我們 Book 的 Struct 遵循 Hashable,想知道的人可以去了解一下 Hashable,這邊你可以想像只要 Book 其中一樣屬性值不一樣,所產生的 hashValue 也是不同的。
讓我們來測試看看吧。

https://ithelp.ithome.com.tw/upload/images/20181020/20107701VFbr87K89O.png

看來真的有重複的書籍資料呢~記得最後把正確的 Array 丟進去啊~

後記:

這次的教學就到這邊結束啦,希望大家在獲取資料時要考慮到可能會發生什麼突發狀況(雖然這種突發通常要先踩了一次才知道),說不定吐出來的資料是出乎你意料的,這時候如果我們能事先考慮到,那我們就可以預防他的發生。

下一次的教學我們就會開始使用這些正確的資料來設計我們的 UI 畫面了 /images/emoticon/emoticon07.gif


上一篇
Day 04: 使用第三方程式庫來幫幫忙!
下一篇
Day 06: 來設計我們的書架吧!
系列文
一天一蘋果,Bug 遠離我。30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言