IOS13 APP『Swift5』實例說明 — 算數練習App(資料篇)

Shin Chao
12 min readDec 23, 2019

--

因為有排行榜的功能,所以必須將一些資料儲存在設備中,這樣才能在每次開啟App時將資料讀回顯示,IOS App 有許多儲存資料的方法,所以這邊就來說一下如何儲存資料與讀取資料吧。

【 UserDefaults】

UserDefaults(user’s defaults database),這是App 預設就有的 property list,只要App存在,就會有這個資料,所以App 啟動後,就會自動將資料載入到記憶體,方便程式快速讀取。

而用來儲存與讀取資料的方法,因該是使用UserDefaults是最簡單的方式,可以用來儲存 Data 型別的資料,只需要利用 PropertyListEncoder 轉換將資料轉換成 Data 型別,這樣的話任何的資料都能存入 UserDefaults。但是不建議大量的資料使用UserDefaults來儲存,像圖檔、影片等等…,因為大量的記憶體會在App啟動後被使用,如果使用太多記憶體,則有App有可能會被 IOS關閉,造成閃退的結果。

那如何存取UserDefaults呢?那首先就要了解可以讀寫的資料型別,可以支援的型別有,Integer、Float、Double、String、Array、Dictionary、Date、Data與URL。那如何讀取與寫入資料到UserDefaults呢?其實就是使用UserDefaults.standard,寫入資料的方法為.set,然後傳入儲存的值與資料名稱。

let userData = UserDefaults.standarduserData.set("儲存的資料值", forKey:"資料的名稱")

讀取資料就是看儲存的值是什麼型別,如果是字串就用.string,如果是整數就用.integer,所以在傳入要讀取的資料名稱。

let userData = UserDefaults.standardlet readData = userData.string(forKey:"資料的名稱")

如果要移除所儲存的資料則使用.removeObject,傳入參數就是要移出的資料名稱,就可以移除資料。

let userData = UserDefaults.standarduserData.removeObject(forKey:"資料的名稱")

一般來說較常使用自訂型別來儲存與讀取資料,所以就來說一下自訂型別如何變成Data來讀取與存入UserDefaults。說到自訂型別那就要先了解Codable,什麼是Codable呢?就是可以編碼與解碼,一般來說都會使用Codable,而不會單獨使用解碼(Decodable)或是編碼(Encodable),因為這樣不管是要解碼(Decodable)或是編碼(Encodable)都可以,因為很少會只編碼(Encodable)後儲存而不讀取,或者是只讀取後只解碼(Decodable),所以這樣也比較方便。

public typealias Codable = Decodable & Encodable

例如有一個自訂的資料如下,那要記得要加上Codable的型別,這樣才能轉成Data。

struct Ranking: Codable{
var name: String
var time: String
var image: String
var medals:String
}
var shoppingUsersRanking = [Ranking]()

如果要將資料編碼後轉成Data,那就要使用PropertyListEncoder()將資料轉成Data在寫入。

let propertyEncoder = PropertyListEncoder()if let data = try? propertyEncoder.encode(shoppingUsersRanking){    let userDefault = UserDefaults.standard    userDefault.set(data, forKey: "shopping")}

如果要將Data讀取後轉成資料,那就要使用PropertyListDecoder()將Data轉換成資料。

let userDefaults = UserDefaults.standardlet propertyDecoder = PropertyListDecoder()if let data = userDefaults.data(forKey: "shopping"), let user = try? propertyDecoder.decode([Ranking].self, from: data){    shoppingUsersRanking = user}

如果想將資料存成JSON的格式也可以,那就要用JSONEncoder編碼,JSONDecoder解碼就好。

【 Document directory】

Document directory就是App儲存資料的目錄,裡面的資料可以備份到iCloud,也可以透過電腦存取這邊的資料,這邊的資料除非使用者移除,不然不會消失,就算App升級就還是會保存在手機中。那接下來就說說如何使用吧。

其實用法與UserDefaults差不多,只是要先透過FileManager.default.urls指定Document directory,for: .documentDirectory是資料夾,而in : .userDomainMask是指電腦或手機使用者的路徑,因為電腦可能有很多的帳號,所以一般而言設定.userDomainMask就可。

let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

一樣要Encoder,所以也是透過PropertyListEncoder()來編碼(Encodable),然後使用FileManager.default.urls所生成的documentsDirectory的.appendingPathComponent方法,來設定要將資料存到哪個檔案,所以要傳入檔名,最後在使用encoder所生成的date,利用.write的方法將資料寫入檔案,檔案不存在時會新增這檔案,若是存在就會被覆蓋。這邊要記得,自訂資料的struct一樣記得要加上Codable的型別,這樣才能轉成Data。

let propertyEncoder = PropertyListEncoder()
if let data = try? propertyEncoder.encode(dreamUsersRanking) {
let url = documentsDirectory.appendingPathComponent("dream")
try? data.write(to: url)
}

這裡或許你會發現,怎麼沒有副檔名,其實可以不用副檔名也可以存擋,如果檔案需要副檔名,則可以使用.appendingPathExtension的法方指定副檔名。

documentsDirectory.appendingPathComponent("dream").appendingPathExtension("txt")

讀取資料也是差不多,也是要先用FileManager.default.urls指定Document directory,

let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

然後也需要decoder,所以一樣是透過PropertyListDecoder()來解碼(Decodable),然後使用FileManager.default.urls所生成的documentsDirectory的.appendingPathComponent方法,來設定要將資料讀取哪個檔案,最後利用.decode的方法將資料讀出。

let propertyDecoder = PropertyListDecoder()
var url = documentsDirectory.appendingPathComponent("shopping")
if let data = try? Data(contentsOf: url), let user = try? propertyDecoder.decode([Ranking].self, from: data){
shoppingUsersRanking = user
}

如果只是需要暫時儲存資料,那可以使用temporaryDirectory,這是暫存目錄,一段時間後就會被移除,所以如果需要暫時儲存時可以使用。

let documentsDirectory = FileManager.default.urls(for: .temporaryDirectory, in: .userDomainMask).first!

如果需要移除檔案那就使用FileManager.default.removeItem將檔案移除。

do {
let url = documentsDirectory.appendingPathComponent("shopping")
try FileManager.default.removeItem(at:url) }
catch { }

最後要注意一點,上面說的方式都是儲存Data的方式,所以若是圖檔,則也需要轉成Data才能儲存。

最後做一個總結,其實UserDefaults或是Document directory的方式讀檔與寫檔,原理上大同小異,主要的步驟如下:

Step 1 : 自訂型別的資料結構,需要加上Codable。

Step2 : 選擇要讀取或寫入的目錄內檔案或是預設的property list。

【UserDefaults】
使用UserDefaults.standard取得預設的property list
【Document directory】
使用FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!取得預設的documentDirectory的目錄,再使用.appendingPathComponent("dream")取得所要存取的檔案

Step3 : 如要讀取就用使用PropertyListDecoder()將Data轉換成資料, 如果要寫入則使用PropertyListEncoder()將資料轉成Data。

【UserDefaults】
寫入資料是對propertyEncoder.encode的data使用.set,將資料寫入。
【Document directory】
寫入資料是對propertyEncoder.encode的data使用.write,將資料寫入。
讀取則都是一樣,使用propertyDecoder.decode後,將資料讀回自訂型別的資料結構的Array。

補充說明:

如果只需要讀檔,那可以有下面幾種方式:

  • 方法1 : 使用Bundle

Step 1 : 將檔案拖曳到Project navigator 清單內。

Step 2 : 使用Bundle.main.url,main bundle所代表的是 App 本身的資料夾,而URL是代表位置,可以是本機端,也可以是遠端的位置。forResource:就是資料檔案名稱,withExtension:就是資料檔案副檔名。

if let url = Bundle.main.url(forResource:"練習用文件",
withExtension: "txt"){
}

Step 3 : 最後就是將資料的資訊轉成Stirng。

if let data = try? String(contentsOf: url){
}
  • 方法2 : 使用NSDataAsset

Step 1 : 將檔案拖曳到Assets內。

Step 2 : 使用NSDataAsset指定Assets內的檔案名稱。

if let asset = NSDataAsset(name: "練習用文件"){
}

Step 3: 最後就是將資料的資訊轉成Stirng,data:就是指Assetss內的Data,encoding:是指檔案的編碼方式。

if let content = String(data: asset.data, encoding: .utf8){
}

--

--