Try   HackMD

CS193P - Lecture 11: Error Handling Persistence

0:44 error handling

9:59 persistence

  1. FileManager (filesystem)
  2. CoreData (SQL database)
  3. CloudKit (database in the cloud)
  4. UserDefault (lightweight data)

12:23 File System

  • file system like a normal Unix filesystem, it starts at /
  • /application: executable, .jpgs, not writable
  • /documents: permanent storage, and always visiable to the user
  • /support: permanent storage not seen directly by the user
  • /caches: store temporary file
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first

17:30 URL & Data

// IMG0011.jpg
func appendingPathComponent(String) -> URL // "IMG0011"
func appendingPathExtension(Stirng) -> URL // "jpg"
// URL
var isFileURL: Bool 
func resourceValues(for keys: [URLResourceKey]) throws -> [URLResourceKey:Any]? // 檔案建立日期等資訊
// Data
init(contentsOf: URL, options: Data.ReadingOptions) throws
func write(to url: URL, options: Data.WritingOptions) throws -> Bool

19:28 Archiving

  • 使用 Codable 把物件轉換成 Data 後存擋
  • 只要 struct 內的資料都有遵守 codable,那麼該 struct 即有遵守 codable (Swift幫我們做了)
  • enum 如果帶有 associate value 的話,要手動處理
  • CodingKeys

手動處理:

struct MyStruct: Codable { var someDate: Date var someString: String var other: SomeOtherType // 非典型型別 init(form decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) someDate = try container.decode(Date.self, forKey: .someDate) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(someDate, forKey: .someDate) } }

29:00 UserDefault

  • String, Int, Bool, Float, Date, Data, Array or Dictionary
let defaults = UserDefault.standard defaults.set(object, forKey: "SomeKey") // object must be a Property List defaults.setDouble(37.5, forKey: "MyDouble") let i: Int = defaults.intefer(forKey: "MyInteger")

34:53 Demo

概要

  • JSON/Codable
  • Dealing with thrown errors
  • saving out EmojiArt document to the File System
  • Implementing the ViewModel for our next MVVM in EmojiArt: PaletteStore
  • UserDefault

點出問題

  • 拖拉圖片、拖拉 emoji 之後
  • 但 xcode rebuild 又要重頭開始
  • 所以我們開始做保存

func save(to url: URL)

  • EmojiArtDocument 內的 emojiArt 是我們要保存的資料
  • EmojiArtModel 弄出一個 func json() -> Data,但會發現沒有遵守 Codable
  • 原來是 EmojiArtModel.Background 這個 enum 沒有遵守 Codable
  • 針對這點,開始實作 init(form decoder: Decode) & func encode(to encoder: Encoder)
// EmojiArtModel enum Background: Equatable, Codable { case blank case url(URL) case imageData(Data) init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let url = try? container.decoder(URL.self, forKey: .url) { self = .url(url) } else if let imageData = try? container.decoder(Data.self, forKey: .imageData) { self = .imageData(imageData) } else { self = .blank } } enum CodingKeys: String, CodingKey { case url = "theURL" case imageData } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .url(let url): try container.encode(url, forKey: .url) case .imageData(let data): try container.encoder(data, forKey: .imageData) case .blank: break } } } func json() throws -> Data { return try JSONEncoder().encoder(self) }
// EmojiArtDocument func save(to url: URL) { do { let data: Data = try emojiArt.json() try data.write(to: url) } catch { print("error = \(error)") } } func autoSave() { if let url = AutoSave.url { save(to: url) } } private struct AutoSave { static let filename = "Autosaved.emojiart" static var url: URL? { let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first return documentDirectory?.appendingPathComponent(filename) } }

現在關掉重開後還是會發現東西要重來,因為還沒做載入

// EmojiArtModel init(json: Data) throws { self = try JSONDecoder().decode(self) } init(url: URL) throws { let data = try Data(contentsOf: url) self = try EmojiArtModel(json: data) }
// EmojiArtDocument init() { if let Autosave.url, let autosavedEmojiArt = try? EmojiArtModel(url: url) { emojiArt = autosavedEmojiArt fetchBackgroundImageDataIfNecessary() } else { emojiArt = EmojiArtModel() } }

在 model 的 didSet 方法內新增 scheduleAutosave()

func scheduleAutosave() { autosaveTimer?.invalidate() autosaveTimer = Timer.scheduelTimer(withTimeInterval: Autosave.coalescingInterval, repeats: false) { _ in self.autosave() } }

1:23:29 Palette

  • 有自己的 MVVM,跟畫布用的不一樣
  • store in userDefault -> HW: iCloud
struct EmojiApp: App { let document = EmojiArtDocument() let paletteStore = PaletteStore(named: "Default") var body: some View { WindowGroup { EmojiArtDocumentView(document: document) } } }