-
-
Save romanroibu/72c4c935136a2dc136b44ad643480529 to your computer and use it in GitHub Desktop.
Code sample for Medium article about Caching and Protocols
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
//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 } | |
} | |
} |
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
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) | |
} | |
} |
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
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) } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thanks man but I am having a little of something from this line:
cacher.load(from: plistStringCache) { dump($0) }
, it throwsfile "note.plist" Not found