Skip to content

Instantly share code, notes, and snippets.

@romanroibu
Forked from raulriera/Cacher.swift
Last active May 14, 2018 03:36
Show Gist options
  • Save romanroibu/72c4c935136a2dc136b44ad643480529 to your computer and use it in GitHub Desktop.
Save romanroibu/72c4c935136a2dc136b44ad643480529 to your computer and use it in GitHub Desktop.
Code sample for Medium article about Caching and Protocols
//This is basically a description of the cache along with the type
public struct Cache<T> {
let fileName: String
let transform: (T) throws -> Data
let reverse: (Data) throws -> T
}
extension Cache {
//You can define a "convenience" initializer that takes a fileName,
//and provides PList serialization as transform and reverse transform operations
public init(plistName: String) {
self.fileName = plistName
self.transform = { try PropertyListSerialization.data(fromPropertyList: $0, format: .xml, options: 0) }
self.reverse = { try PropertyListSerialization.propertyList(from: $0, options: [], format: nil) as! T }
}
}
public final class Cacher {
let destination: URL
private let queue = OperationQueue()
public enum CacheDestination {
case temporary
case atFolder(String)
}
// MARK: Initialization
public init(destination: CacheDestination) {
// Create the URL for the location of the cache resources
switch destination {
case .temporary:
self.destination = URL(fileURLWithPath: NSTemporaryDirectory())
case .atFolder(let folder):
let documentFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
self.destination = URL(fileURLWithPath: documentFolder).appendingPathComponent(folder, isDirectory: true)
}
let fileManager = FileManager.default
do {
try fileManager.createDirectory(at: self.destination, withIntermediateDirectories: true, attributes: nil)
}
catch {
fatalError("Unable to create cache URL: \(error)")
}
}
public func persist<T>(_ item: T, at cache: Cache<T>, completion: @escaping (_ url: URL) -> Void) {
let url = destination.appendingPathComponent(cache.fileName, isDirectory: false)
// Create an operation to process the request.
let operation = BlockOperation {
do {
try cache.transform(item).write(to: url, options: [.atomicWrite])
} catch {
fatalError("Failed to write item to cache: \(error)")
}
}
// Set the operation's completion block to call the request's completion handler.
operation.completionBlock = {
completion(url)
}
// Add the operation to the queue to start the work.
queue.addOperation(operation)
}
public func load<T>(from cache: Cache<T>, completion: @escaping (_ item: T) -> Void) {
let url = destination.appendingPathComponent(cache.fileName, isDirectory: false)
// Create an operation to process the request.
let operation = BlockOperation {
do {
let data = try Data(contentsOf: url, options: [])
let item = try cache.reverse(data)
completion(item)
} catch {
fatalError("Failed to read item from cache: \(error)")
}
}
// Add the operation to the queue to start the work.
queue.addOperation(operation)
}
}
let cacher = Cacher(destination: .temporary)
//You can have a cache that stores any valid JSON, not only dictionaries
let jsonCache = Cache(
fileName: "cached_server_response.json",
transform: { try! JSONSerialization.data(withJSONObject: $0, options: []) },
reverse: { try! JSONSerialization.jsonObject(with: $0, options: []) }
)
//You can restrict the type of the config to be a dictionary
//So that you always get a valid dictionary with String keys
//Or you can further restrict it to have only Int values, if that is your use-case
let configPlist = Cache<[String: Any]>(plistName: "config.plist")
//You can have caches that store the same underlying type (String)
//But have diferent strategies for turning those types into data, and back
let noteCache = Cache<String>(
fileName: "note.txt",
transform: { $0.data(using: .utf8)! },
reverse: { String(data: $0, encoding: .utf8)! }
)
let plistStringCache = Cache<String>(plistName: "note.plist")
// Contents of file will be:
//"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<string>Hello, World!</string>\n</plist>\n"
cacher.persist("Hello, World!", at: plistStringCache) {
//dump($0)
let data = try! Data(contentsOf: $0)
let string = String(data: data, encoding: .utf8)!
dump(string)
}
// Contents of file will be:
// "Hello, World!"
cacher.persist("Hello, World!", at: noteCache) {
//dump($0)
let data = try! Data(contentsOf: $0)
let string = String(data: data, encoding: .utf8)!
dump(string)
}
cacher.load(from: plistStringCache) { dump($0) }
cacher.load(from: noteCache) { dump($0) }
@siempay
Copy link

siempay commented Sep 13, 2017

thanks man but I am having a little of something from this line: cacher.load(from: plistStringCache) { dump($0) }, it throws file "note.plist" Not found

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