In a recent project I leveraged the Codable protocol to save API responses to disk to make the application more responsive (and to have an offline mode). I was happy with the results. In this episode we will add some caching to an existing application, saving JSON responses to the caches directory on the device.
Setting up storage options We'll want to be able to easily switch the location of the stored files. We'll start by creating an enum to describe the types of data we will store: enum StorageType { case cache case permanent var searchPathDirectory: FileManager.SearchPathDirectory { switch self { case .cache: return .cachesDirectory case .permanent: return .documentDirectory } } var folder: URL { let path = NSSearchPathForDirectoriesInDomains(searchPathDirectory, .userDomainMask, true).first! let subfolder = "com.nsscreencast.TopRepos.json_storage" return URL(fileURLWithPath: path).appendingPathComponent(subfolder) } func clearStorage() { try? FileManager.default.removeItem(at: folder) } } This will allow us to specify that we want data to be stored in the Caches folder, allowing the OS to clean it up as it sees fit. If you have data that needs to be durable (say, like profile information for the currently logged in user), then you can use the Documents folder. Saving / Loading Data on Disk Next we'll create our storage class. We'll call it LocalJSONStore. It will be generic over the type of data we wish to save, so we will need to make sure that the generic type is Codable: class LocalJSONStore<T> where T : Codable { let storageType: StorageType let filename: String init(storageType: StorageType, filename: String) { self.storageType = storageType self.filename = filename ensureFolderExists() } // ... snip } Saving an object func save(_ object: T) { do { let data = try JSONEncoder().encode(object) try data.write(to: fileURL) } catch let e { print("ERROR: \(e)") } } Retrieving a saved object var storedValue: T? { guard FileManager.default.fileExists(atPath: fileURL.path) else { return nil } do { let data = try Data(contentsOf: fileURL) let jsonDecoder = JSONDecoder() return try jsonDecoder.decode(T.self, from: data) } catch let e { print("ERROR: \(e)") return nil } } It is important to note that errors are to be expected here. The data may become outdated as the application is updated, and so it may fail to decode back into your object. If this happens it is okay because we just won't have cached data, and the application will have to fetch and save a new copy.