天天看點

Swift:緩存Codable資料

我們的大多數應用程式都是某些後端的REST用戶端。在開發此類應用程式期間,我們希望使其保持脫機狀态。在這種情況下,我們必須将資料緩存在裝置本地的某處,以使其無需網際網路即可讀取。

Apple提供了

CoreData

架構,這是在本地存儲應用程式資料的最佳方法。它具有許多出色的功能,可幫助您促進開發。但是,很難将其用作簡單的緩存。大多數時候,我們隻需要顯示緩存的資料,而無需任何其他操作。我認為,我們所需要的隻是純磁盤存儲。本周,我們将讨論如何輕松地為

Codable

結構實作簡單的磁盤存儲。

Swift:緩存Codable資料

CodableStorage

首先,為我們的存儲邏輯定義幾個協定。我想分開通路存儲的可寫和可讀部分,這是我們可以使用Swift語言的協定組合功能的地方。

import Foundation

typealias Handler<T> = (Result<T, Error>) -> Void

protocol ReadableStorage {
    func fetchValue(for key: String) throws -> Data
    func fetchValue(for key: String, handler: @escaping Handler<Data>)
}

protocol WritableStorage {
    func save(value: Data, for key: String) throws
    func save(value: Data, for key: String, handler: @escaping Handler<String>)
}

typealias Storage = ReadableStorage & WritableStorage           

複制

首先,讓我們描述一些用于存儲的根路徑的變量,用于異步工作的

DispatchQueue

FileManager

,我們将使用它們來浏覽檔案系統。

class DiskStorage {
    private let path: URL
    private let queue: DispatchQueue
    private let fileManager: FileManager
    
    init(
        path: URL,
        queue: DispatchQueue = .init(label: "DiskCache.Queue"),
        fileManager: FileManager = FileManager.default
    ) {
        self.path = path
        self.queue = queue
        self.fileManager = fileManager
    }
    
    private func createFolders(in url: URL) throws {
        let folderUrl = url.deletingLastPathComponent()
        if !fileManager.fileExists(atPath: folderUrl.path) {
            try fileManager.createDirectory(
                at: folderUrl,
                withIntermediateDirectories: true,
                attributes: nil)
        }
    }
}           

複制

下一步是實作我們存儲的可寫部分。這有點棘手,因為key是檔案系統上資料的路徑。是以,我們需要将ke'y附加到根路徑并生成用于存儲資料的新URL。新URL可以包含子檔案夾,這就是我們建立

createFolders

函數的原因,該函數根據路徑建立所需的檔案夾。

extension DiskStorage: WritableStorage {
    func save(value: Data, for key: String) throws {
        let url = path.appendingPathComponent(key)
        do {
            try self.createFolders(in: url)
            try value.write(to: url, options: .atomic)
        } catch {
            throw StorageError.cantWrite(error)
        }
    }
}           

複制

這是存儲協定的可讀部分,我們在其中為傳遞的key實作資料擷取。同樣,我們使用key作為磁盤上資料的路徑。

extension DiskStorage: ReadableStorage {
    func fetchValue(for key: String) throws -> Data {
        let url = path.appendingPathComponent(key)
        guard let data = fileManager.contents(atPath: url.path) else {
            throw StorageError.notFound
        }
        return data
    }
}           

複制

現在我們有了一個簡單的磁盤存儲的工作示例。下一步是為我們的

DiskStorage

類實作一個簡單的擴充卡,該擴充卡将處理JSON編碼/解碼。

class CodableStorage {
    private let path: URL
    private var storage: DiskStorage
    private let decoder: JSONDecoder
    private let encoder: JSONEncoder
    
    init(
        path: URL = URL(fileURLWithPath:  NSTemporaryDirectory()),
        decoder: JSONDecoder = .init(),
        encoder: JSONEncoder = .init()
    ) {
        self.path = path
        self.storage = DiskStorage(path: path)
        self.decoder = decoder
        self.encoder = encoder
    }
    
    func fetch<T: Decodable>(for key: String) throws -> T {
        let data = try storage.fetchValue(for: key)
        return try decoder.decode(T.self, from: data)
    }
    
    func save<T: Encodable>(_ value: T, for key: String) throws {
        let data = try encoder.encode(value)
        try storage.save(value: data, for: key)
    }
}           

複制

CodableStorage

類包裝我們的

DiskStorage

類以添加JSON編解碼邏輯。它使用通用限制來了解如何解碼和編碼資料。現在該在實際示例中使用我們的

CodableStorage

了。

#### 使用示例:

struct Timeline: Codable {
    let tweets: [String]
}

let storage = CodableStorage()

let timeline = Timeline(tweets: ["Hello", "World", "!!!"])

// -----同步:-----

//寫
do {
    try storage.save(timeline, for: "timeline")
} catch  {
    print(error)
}

//讀
if let cached: Timeline = try? storage.fetch(for: "timeline")  {
    print(cached)
}

//讀
do {
    let cached: Timeline = try storage.fetch(for: "timeline")
    print(cached)
} catch {
    print(error)
}

//删單個
do {
    try storage.delete(for: "timeline")
} catch  {
    print(error)
}

//删所有
do {
    try storage.deleteAll()
} catch  {
    print(error)
}

// -----異步:-----

//存
storage.asyncSave(timeline, for: "timeline") { result in
    switch result {
    case .success(let key):
        print("async save \(key) success")
    case .failure(let error):
        print(error)
    }
}

//取
storage.asyncFetch(for: "timeline") { (result: Result<Timeline, Error>) in
    switch result {
    case .success(let timeline):
        print(timeline)
    case .failure(let err):
        print(err)
    }
}

//删單個
storage.asyncDelete(for: "timeline") { (result) in
    switch result {
    case .success(_):
        print("删除成功")
    case .failure(let error):
        print(error)
    }
}

//删除全部
storage.asyncDeleteAll() { (result) in
    switch result {
    case .success(_):
        print("删除成功")
    case .failure(let error):
        print(error)
    }
}           

複制

在上面的代碼示例中,您可以看到

CodableStorage

類的用法。

Timeline

是一個簡單的遵循

Codable

協定的結構體,表示存儲在

CodableStorage

中的字元串數組。

今天,我們讨論了一種可存儲可編碼結構的簡單方法,該結構可通過REST API擷取。有時候,我們不需要CoreData的複雜功能即可進行簡單的JSON緩存,這足以實作磁盤存儲。

DEMO已經上傳至GitHub:CodableStorage,将

CodableStorage

檔案夾拖到項目即可直接使用。

DEMO已實作功能:同步/異步 存,取,删

本文内容來自https://swiftwithmajid.com/2019/05/22/storing-codable-structs-on-the-disk/

對原文進行了簡單翻譯,同時修改了部分代碼,便于使用