# 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)
}
}
}
```