# CS193P - Lecture 11: Error Handling Persistence #### ~~0:44 error handling~~ - 請自行翻書 [swift-book/ErrorHandling](https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html) #### ~~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 ```swift let manager = FileManager.default let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first ``` #### ~~17:30 URL & Data~~ ```swift // IMG0011.jpg func appendingPathComponent(String) -> URL // "IMG0011" func appendingPathExtension(Stirng) -> URL // "jpg" ``` ```swift // URL var isFileURL: Bool func resourceValues(for keys: [URLResourceKey]) throws -> [URLResourceKey:Any]? // 檔案建立日期等資訊 ``` ```swift // 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 手動處理: ```swift= 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` ```swift= 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)` ```swift= // 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) } ``` ```swift= // 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) } } ``` 現在關掉重開後還是會發現東西要重來,因為還沒做載入 ```swift= // 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) } ``` ```swift= // EmojiArtDocument init() { if let Autosave.url, let autosavedEmojiArt = try? EmojiArtModel(url: url) { emojiArt = autosavedEmojiArt fetchBackgroundImageDataIfNecessary() } else { emojiArt = EmojiArtModel() } } ``` 在 model 的 didSet 方法內新增 scheduleAutosave() ```swift= 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 ```swift= struct EmojiApp: App { let document = EmojiArtDocument() let paletteStore = PaletteStore(named: "Default") var body: some View { WindowGroup { EmojiArtDocumentView(document: document) } } } ```