-
-
Save andrewcampoli/aa8de5a2ef64a8a0b48d7a1f9c3269da to your computer and use it in GitHub Desktop.
Helper class to easily store and retrieve Codable structs from/to disk. https://medium.com/@sdrzn/swift-4-codable-lets-make-things-even-easier-c793b6cf29e1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
protocol Encoder { | |
func encode<Value>(_ value: Value) throws -> Data where Value : Encodable | |
} | |
extension JSONEncoder: Encoder {} | |
extension PropertyListEncoder: Encoder {} | |
protocol Decoder { | |
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable | |
} | |
extension JSONDecoder: Decoder {} | |
extension PropertyListDecoder: Decoder {} | |
public class Storage { | |
static private let jsonEncoder = JSONEncoder() | |
static private let jsonDecoder = JSONDecoder() | |
static private let plistEncoder = PropertyListEncoder() | |
static private let plistDecoder = PropertyListDecoder() | |
init() { } | |
enum Format: String { | |
case plist = "plist", json = "json" | |
} | |
enum Directory { | |
// Only documents and other data that is user-generated, or that cannot otherwise be recreated by your application, should be stored in the <Application_Home>/Documents directory and will be automatically backed up by iCloud. | |
case documents | |
// Data that can be downloaded again or regenerated should be stored in the <Application_Home>/Library/Caches directory. Examples of files you should put in the Caches directory include database cache files and downloadable content, such as that used by magazine, newspaper, and map applications. | |
case caches | |
} | |
private static func convertFileNameToFormat(_ fileName: String) -> Format? { | |
if let range = fileName.range(of: ".") { | |
let formatString = fileName[range.upperBound...] | |
return Format(rawValue: String(formatString)) | |
} else { | |
return nil | |
} | |
} | |
/// Returns URL constructed from specified directory | |
static private func getURL(for directory: Directory) -> URL { | |
var searchPathDirectory: FileManager.SearchPathDirectory | |
switch directory { | |
case .documents: | |
searchPathDirectory = .documentDirectory | |
case .caches: | |
searchPathDirectory = .cachesDirectory | |
} | |
if let url = FileManager.default.urls(for: searchPathDirectory, in: .userDomainMask).first { | |
return url | |
} else { | |
fatalError("Could not create URL for specified directory!") | |
} | |
} | |
/// Store an encodable struct to the specified directory on disk | |
/// | |
/// - Parameters: | |
/// - object: the encodable struct to store | |
/// - directory: where to store the struct | |
/// - fileName: what to name the file where the struct data will be stored | |
static func store<T: Encodable>(_ object: T, to directory: Directory, as fileName: String) { | |
let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false) | |
guard let format = convertFileNameToFormat(fileName) else { | |
fatalError("Given file format: \(fileName) is not supported.") | |
} | |
let encoder: Encoder | |
switch format { | |
case .json: | |
encoder = jsonEncoder | |
case .plist: | |
encoder = plistEncoder | |
} | |
do { | |
let data = try encoder.encode(object) | |
if FileManager.default.fileExists(atPath: url.path) { | |
try FileManager.default.removeItem(at: url) | |
} | |
FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil) | |
} catch { | |
fatalError(error.localizedDescription) | |
} | |
} | |
/// Retrieve and convert a struct from a file on disk | |
/// | |
/// - Parameters: | |
/// - fileName: name of the file where struct data is stored | |
/// - directory: directory where struct data is stored | |
/// - type: struct type (i.e. Message.self) | |
/// - Returns: decoded struct model(s) of data | |
static func retrieve<T: Decodable>(_ fileName: String, from directory: Directory, as type: T.Type) -> T { | |
let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false) | |
if !FileManager.default.fileExists(atPath: url.path) { | |
fatalError("File at path \(url.path) does not exist!") | |
} | |
guard let format = convertFileNameToFormat(fileName) else { | |
fatalError("Given file format: \(fileName) is not supported.") | |
} | |
let decoder: Decoder | |
switch format { | |
case .json: | |
decoder = jsonDecoder | |
case .plist: | |
decoder = plistDecoder | |
} | |
if let data = FileManager.default.contents(atPath: url.path) { | |
do { | |
let model = try decoder.decode(type, from: data) | |
return model | |
} catch { | |
fatalError(error.localizedDescription) | |
} | |
} else { | |
fatalError("No data at \(url.path)!") | |
} | |
} | |
/// Remove all files at specified directory | |
static func clear(_ directory: Directory) { | |
let url = getURL(for: directory) | |
do { | |
let contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) | |
for fileUrl in contents { | |
try FileManager.default.removeItem(at: fileUrl) | |
} | |
} catch { | |
fatalError(error.localizedDescription) | |
} | |
} | |
/// Remove specified file from specified directory | |
static func remove(_ fileName: String, from directory: Directory) { | |
let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false) | |
if FileManager.default.fileExists(atPath: url.path) { | |
do { | |
try FileManager.default.removeItem(at: url) | |
} catch { | |
fatalError(error.localizedDescription) | |
} | |
} | |
} | |
/// Returns BOOL indicating whether file exists at specified directory with specified file name | |
static func fileExists(_ fileName: String, in directory: Directory) -> Bool { | |
let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false) | |
return FileManager.default.fileExists(atPath: url.path) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment