Skip to content

Instantly share code, notes, and snippets.

@thecoolwinter
Created August 6, 2020 16:53
Show Gist options
  • Save thecoolwinter/dea0a68ac8957f1213f9a5f6ea5a0f40 to your computer and use it in GitHub Desktop.
Save thecoolwinter/dea0a68ac8957f1213f9a5f6ea5a0f40 to your computer and use it in GitHub Desktop.
import Foundation
public class CodableStorage {
fileprivate init() { }
enum Directory {
case documents
case caches
case shared // Use for sharing files between containers. Eg, with an app extension.
}
/// Returns URL constructed from specified directory
static fileprivate func getURL(for directory: Directory) -> URL {
var searchPathDirectory: FileManager.SearchPathDirectory
switch directory {
case .documents:
searchPathDirectory = .documentDirectory
case .caches:
searchPathDirectory = .cachesDirectory
case .shared:
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: <#Bundle ID#>) ?? FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
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, encryption: URLFileProtection?) {
let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false)
let encoder = JSONEncoder()
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)
if encryption != nil {
try (url as NSURL).setResourceValue(encryption!, forKey: .fileProtectionKey)
}
} 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) {
print("File at path \(url.path) does not exist!")
return nil
}
if let data = FileManager.default.contents(atPath: url.path) {
let decoder = JSONDecoder()
do {
let model = try decoder.decode(type, from: data)
return model
} catch {
print(error.localizedDescription)
return nil
}
} else {
print("No data at \(url.path)!")
return nil
}
}
/// 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)
}
}
@thecoolwinter
Copy link
Author

Use this class like so:

CodableStorage.store(CodableObject, to: .directory, as: "Filename", encryption: nil)

The encryption enum simply adds apple's native file protection, it's not actually encryption. Also, there's an option in the Directory enum for a shared file location, which will require you to have an ID for a shared container, such as for an app extension.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment