Type 'Cache' does not conform to protocol 'Encodable' - swift

I have been following the guide found here for Caching in Swift.
I am currently getting the error Type 'Cache' does not conform to protocol 'Encodable'
This makes no sense to me as I have followed the guide to the letter, I have found a couple of people using the same cache on GitHub and I believe my output matches theirs.
Why does 'Cache' not conform?
I have added the complete class as created in the tutorial below -
final class Cache<Key: Hashable, Value> {
private let wrapped = NSCache<WrappedKey, Entry>()
private let entryLifetime: TimeInterval
private let dateProvider: () -> Date
private let keyTracker = KeyTracker()
init(dateProvider: #escaping () -> Date = Date.init,entryLifetime: TimeInterval = 12 * 60 * 60, maximumEntryCount: Int = 50) {
self.dateProvider = dateProvider
self.entryLifetime = entryLifetime
wrapped.countLimit = maximumEntryCount
wrapped.delegate = keyTracker
}
func insert(_ value: Value, forKey key: Key) {
let date = dateProvider().addingTimeInterval(entryLifetime)
let entry = Entry(key: key, value: value, expirationDate: date)
let wrappedKey = WrappedKey(key: key)
wrapped.setObject(entry, forKey: wrappedKey)
keyTracker.keys.insert(key)
}
func value(forKey key: Key) -> Value? {
guard let entry = wrapped.object(forKey: WrappedKey(key: key)) else { return nil }
guard dateProvider() < entry.expirationDate else {
//Discard expired values
removeValue(forKey: key)
return nil
}
return entry.value
}
func removeValue(forKey key: Key) {
let key = WrappedKey(key: key)
wrapped.removeObject(forKey: key)
}
}
private extension Cache {
final class WrappedKey: NSObject {
let key: Key
init(key: Key) {
self.key = key
}
override var hash: Int { return key.hashValue }
override func isEqual(_ object: Any?) -> Bool {
guard let value = object as? WrappedKey else { return false }
return value.key == key
}
}
}
private extension Cache {
final class Entry {
let key: Key
let value: Value
let expirationDate: Date
init(key: Key, value: Value, expirationDate: Date) {
self.key = key
self.value = value
self.expirationDate = expirationDate
}
}
}
extension Cache {
subscript(key: Key) -> Value? {
get { return value(forKey: key) }
set {
guard let value = newValue else {
//If nil is assigned using subscript then remove any value for that key
removeValue(forKey: key)
return
}
insert(value, forKey: key)
}
}
}
private extension Cache {
final class KeyTracker: NSObject, NSCacheDelegate {
var keys = Set<Key>()
func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject object: Any) {
guard let entry = object as? Entry else { return }
keys.remove(entry.key)
}
}
}
extension Cache.Entry: Codable where Key: Codable, Value: Codable {}
private extension Cache {
func entry(forKey key: Key) -> Entry? {
guard let entry = wrapped.object(forKey: WrappedKey(key: key)) else { return nil }
guard dateProvider() < entry.expirationDate else {
removeValue(forKey: key)
return nil
}
return entry
}
func insert(_ entry: Entry) {
wrapped.setObject(entry, forKey: WrappedKey(key: entry.key))
keyTracker.keys.insert(entry.key)
}
}
extension Cache: Codable where Key: Codable, Value: Codable {
convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.singleValueContainer()
let entries = try container.decode([Entry].self)
entries.forEach(insert)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(keyTracker.keys.compactMap(entry))
}
}
extension Cache where Key: Codable, Value: Codable {
func saveToDisk(withName name: String, using fileManager: FileManager = .default) throws {
let folderURLs = fileManager.urls(
for: .cachesDirectory,
in: .userDomainMask
)
let fileURL = folderURLs[0].appendingPathComponent(name + ".cache")
let data = try JSONEncoder().encode(self)
try data.write(to: fileURL)
}
}

Related

Add multiple Structs to one UserDefaults entity

I have my UserDefaults like this
fileprivate enum userdefaultKeys: String {
userSearchHistory = "userSearchHistory",
}
extension UserDefaults {
static func getUserSearchHistory() -> SearchHistory? {
let data = self.standard.data(forKey: userdefaultKeys.userSearchHistory.rawValue)
return SearchHistory.decode(json: data)
}
static func setUserSearchHistory(userSearchHistory: SearchHistory?) {
guard let json: Any = userSearchHistory?.json else { return }
self.standard.set(json, forKey: userdefaultKeys.userSearchHistory.rawValue)
}
}
And I'm saving this data to the UserDefaults
struct SearchHistory: Codable {
let type: SearchHistoryEnum
let name: String
let corpNo: String
let storeNo: String
let long: Double
let lat: Double
}
enum SearchHistoryEnum: Codable {
case storeSearch
case jsonSearch
}
let historySearch = SearchHistory(type: SearchHistoryEnum.storeSearch, name: store?.storename ?? "", corpNo: store?.corpno ?? "", storeNo: store?.storeno ?? "", long: longtitude, lat: latitude)
UserDefaults.setUserSearchHistory(userSearchHistory: historySearch)
This is okay, but it saves only one instance of SearchHistory in the time. I would like to have max 5. When 6th instance comes, I would like to delete the most old one
First of all your enum doesn't compile, you probably mean
fileprivate enum UserdefaultKeys: String {
case userSearchHistory
}
It's not necessary to specify the raw value.
Second of all I highly recommend not to fight the framework and to conform to the UserDefaults pattern to overload the getter and setter. I don't know your special methods for decoding and encoding, this is a simple implementation with standard JSONDecoder and JSONEncoder
extension UserDefaults {
func searchHistory(forKey key: String = UserdefaultKeys.userSearchHistory.rawValue) -> SearchHistory? {
guard let data = data(forKey: key) else { return nil }
return try? JSONDecoder().decode(SearchHistory.self, from: data)
}
func searchHistory(forKey key: String = UserdefaultKeys.userSearchHistory.rawValue) -> [SearchHistory]? {
guard let data = data(forKey: key) else { return nil }
return try? JSONDecoder().decode([SearchHistory].self, from: data)
}
func set(_ value: SearchHistory, forKey key: String = UserdefaultKeys.userSearchHistory.rawValue) {
guard let data = try? JSONEncoder().encode(value) else { return }
set(data, forKey: key)
}
func set(_ value: [SearchHistory], forKey key: String = UserdefaultKeys.userSearchHistory.rawValue) {
guard let data = try? JSONEncoder().encode(value) else { return }
set(data, forKey: key)
}
}
The benefit is the syntax is always the same for a single item or for an array
let historySearch = SearchHistory(type: SearchHistoryEnum.storeSearch, name: store?.storename ?? "", corpNo: store?.corpno ?? "", storeNo: store?.storeno ?? "", long: longtitude, lat: latitude)
UserDefaults.standard.set(historySearch)
or
UserDefaults.standard.set([historySearch])
The code to delete the oldest put in the method to write the data.

Creating a expiry date for UserDefaults values

So I had this idea of creating an expiry for UserDefaults. This is the approach I was starting to take but I'm stuck.
struct TimedObject<T: Codable>: Codable {
let object: T
let expireDate: Date
}
and then:
extension UserDefaults {
func set<T: Codable>(_ value: T, forKey key: String, expireDate: Date) {
let timedObject = TimedObject<T>(object: value, expireDate: expireDate)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(timedObject) {
UserDefaults.standard.set(encoded, forKey: key)
}
override open class func value(forKey key: String) -> Any? {
guard let value = self.value(forKey: key) else {
return nil
}
if TimedObject<???>.self == type(of: value) { // This is where I'm stuck
...
}
}
So if I would name the type and not use generics I would easily solve this. But naturally I prefer to use generics. Can this be done?
I know OP is using a struct to wrap the stored value but I would still like to offer a different protocol based solution where any type that should be stored with an expiration date needs to conform to this protocol.
Here is the protocol for I am using
protocol TimedObject: Codable {
associatedtype Value: Codable
var value: Value { get }
var expirationDate: Date { get }
}
and the functions to store and retrieve from UserDefaults
extension UserDefaults {
func set<Timed: TimedObject>(_ value: Timed, forKey key: String) {
if let encoded = try? JSONEncoder().encode(value) {
self.set(encoded, forKey: key)
}
}
func value<Timed: TimedObject>(_ type: Timed.Type, forKey key: String) -> Timed.Value? {
guard let data = self.value(forKey: key) as? Data, let object = try? JSONDecoder().decode(Timed.self, from: data) else {
return nil
}
return object.expirationDate > .now ? object.value : nil
}
}
Finally an example
struct MyStruct: Codable {
let id: Int
let name: String
}
extension MyStruct: TimedObject {
typealias Value = Self
var value: MyStruct { self }
var expirationDate: Date {
.now.addingTimeInterval(24 * 60 * 60)
}
}
let my = MyStruct(id: 12, name: "abc")
UserDefaults.standard.set(my, forKey: "my")
let my2 = UserDefaults.standard.value(MyStruct.self, forKey: "my")
Since you're returning Any?, it is best to create another struct to point to TimedObject as you don't need the object property when returning Any?:
struct Expiry: Codable {
var expireDate: Date
}
struct TimedObject<T: Codable>: Timable {
let object: T
var expireDate: Date
}
override open class func value(forKey key: String) -> Any? {
guard let value = self.value(forKey: key) as? Data else {
return nil
}
if let timed = try? JSONDecoder().decode(Expiry.self, from: value) {
//do anything with timed
}
//make sure to return value
}
And here's a method to access TimedObject:
func timedObject<T: Codable>(forKey key: String) -> TimedObject<T>? {
guard let value = self.data(forKey: key) as? Data, let timed = try? JSONDecoder().decode(TimedObject<T>.self, from: value) else {
return nil
}
return value
}
If you make the value() method generic then you can reverse the process done in the set() method: retrieve the data and decode it as a TimedObject<T>.
However, I would choose a different name to avoid possible ambiguities with the exisiting value(forKey:) method. Also I see no reason why this should be a class method.
Note also that your generic set() method should call the non-generic version on the same instance.
extension UserDefaults {
func set<T: Codable>(_ value: T, forKey key: String, expireDate: Date) {
let timedObject = TimedObject(object: value, expireDate: expireDate)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(timedObject) {
set(encoded, forKey: key)
}
}
func expiringValue<T: Codable>(forKey key: String) -> T? {
guard let data = self.data(forKey: key) else {
return nil
}
let decoder = JSONDecoder()
guard let decoded = try? decoder.decode(TimedObject<T>.self, from: data) else {
return nil
}
// check expire date ...
return decoded.object
}
}
Example usage:
let val1 = UserDefaults.standard.expiringValue(forKey: "foo") as String?
let val2: String? = UserDefaults.standard.expiringValue(forKey: "bar")
In both cases, expiringValue(forKey:) is called with the inferred type.
Or in combination with optional binding:
if let val: String = UserDefaults.standard.expiringValue(forKey: "test") {
print(val)
}
Another option is to pass the desired type as an additional argument:
func value<T: Codable>(forKey key: String, as: T.Type) -> T? {
guard let data = self.data(forKey: key) else {
return nil
}
let decoder = JSONDecoder()
guard let decoded = try? decoder.decode(TimedObject<T>.self, from: data) else {
return nil
}
// check expire date ...
return decoded.object
}
which is then used as
let val = UserDefaults.standard.value(forKey: "foo", as: String.self)

Generic Function: Type of expression is ambiguous without more context

I have a static function in a class that takes a generic type that must conform to decodable, however when I call this function I get the following error: "Type of expression is ambiguous without more context". The Tour class (which is the type I'm passing to the function) conforms to Decodable and inherits from the CoreDataModel class.
This is occurring on the new Xcode 12.0 Beta in DashboardNetworkAdapter when I call CoreDataModel.create (line 7 on the snippet I've shared for that class).
Edit minimal reproducible example:
DashboardNetworkAdapter:
class DashboardNetworkAdapter {
// MARK: - GET
public func syncTours(page: Int, completion: #escaping(Bool, Error, Bool) -> Void) {
if let path = Bundle.main.path(forResource: "Tours", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
CoreDataModel.create(Array<Tour>.self, from: data) { success, error in
completion(success, error, false)
}
} catch let error {
completion(false, error, false)
}
}
}
}
CoreDataModel:
public class CoreDataModel: NSManagedObject {
// MARK: - Variables
#NSManaged public var id: Int64
#NSManaged public var createdAt: Date?
#NSManaged public var updatedAt: Date?
// MARK: - CRUD
static func create<T>(_ type: T.Type, from data: Data, completion: #escaping (Bool, Error?) -> Void) where T : Decodable {
DataCoordinator.performBackgroundTask { context in
do {
let _ = try DataDecoder(context: context).decode(type, from: data, completion: {
completion(true, nil)
})
} catch let error {
completion(false, error)
}
}
}
}
Tour:
#objc(Tour)
class Tour: CoreDataModel, Decodable {
// MARK: - Variables
#NSManaged public var name: String?
#NSManaged public var image: URL?
#NSManaged public var owned: Bool
#NSManaged public var price: Double
// MARK: - Coding Keys
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case image = "image"
case owned = "owned"
case price = "price"
case updatedAt = "updated_at"
case createdAt = "created_at"
}
// MARK: - Initializer
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Cannot find decoding context")}
self.init(context: context)
let topLevel = try decoder.container(keyedBy: PaginatedResponseCodingKeys.self)
let values = try topLevel.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
let id = try values.decode(Int64.self, forKey: .id)
let name = try values.decode(String.self, forKey: .name)
let image = try values.decode(String.self, forKey: .image)
let owned = try values.decode(Int.self, forKey: .owned)
let price = try values.decode(Double.self, forKey: .price)
let updatedAt = try values.decode(Date.self, forKey: .updatedAt)
let createdAt = try values.decode(Date.self, forKey: .createdAt)
self.id = id
self.name = name
self.image = URL(string: image)
self.owned = owned == 1
self.price = price
self.updatedAt = updatedAt
self.createdAt = createdAt
}
init(context: NSManagedObjectContext) {
guard let entity = NSEntityDescription.entity(forEntityName: "Tour", in: context) else { fatalError() }
super.init(entity: entity, insertInto: context)
}
}
DataDecoder:
class DataDecoder: JSONDecoder {
// MARK: - Variables
var context: NSManagedObjectContext!
private var persistent: Bool = true
// MARK: - Initializers
public init(persistent: Bool = true, context: NSManagedObjectContext) {
super.init()
self.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
let dateFormatter = DateFormatter()
locale = .autoupdatingCurrent
dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.date(from: string)!
})
self.context = context
userInfo[.context] = context
}
// MARK: - Utilities
public func decode<T>(_ type: T.Type, from data: Data, completion: (() -> Void)? = nil) throws -> T where T : Decodable {
let result = try super.decode(type, from: data)
if(persistent) {
saveContext(completion: completion)
}
return result
}
private func saveContext(completion: (() -> Void)? = nil) {
guard let context = context else { fatalError("Cannot Find Decoding Context") }
context.performAndWait {
try? context.save()
completion?()
context.reset()
}
}
}
CodingUserInfoKey Extension:
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")!
}
There's a type mismatch between what completion closure expects for its Error parameter, and what it gets from the inferred type of the CoreDataModel.create completion closure, which is Error?:
public func syncTours(page: Int, completion: #escaping(Bool, Error, Bool) -> Void) {
...
CoreDataModel.create(Array<Tour>.self, from: data) { success, error in
// error is of type Error?, but completion expects Error
completion(success, error, false)
}
...
}
In general, whenever you face type inference issues or errors, make each type explicit, and you'll see exactly exactly where the mismatch is. For example, below you could be explicit about the inner closure signature:
CoreDataModel.create([Tour].self, from: data) { (success: Bool, err: Error?) -> Void in
}

Realm accessed from incorrect thread occasional

I have this function
class func addCVals(_ criteres: [[AnyHashable: Any]], _ type: String) {
DispatchQueue.global(qos: .background).async {
autoreleasepool {
if criteres.count > 0 {
if let realm = DBTools.getRealm() {
do {
try realm.transaction {
let oldValues = CriteresVal.objects(in: realm, where: "type = '\(type)'")
if oldValues.count > 0 {
realm.deleteObjects(oldValues)
}
for critere in criteres {
let cval = CriteresVal(critere, type)
if let c = cval {
realm.addOrUpdate(c)
}
}
}
} catch {
DebugTools.record(error: error)
}
realm.invalidate()
}
}
}
}
}
The request that get oldValues occasionally cause an error
Realm accessed from incorrect thread
I don't understand why as I get a new Realm before with this lines:
if let realm = DBTools.getRealm()
My function getRealm:
class func getRealm() -> RLMRealm? {
if !AppPreference.lastAccount.elementsEqual("") {
let config = RLMRealmConfiguration.default()
do {
return try RLMRealm(configuration: config)
} catch {
DebugTools.record(error: error)
DispatchQueue.main.async {
Notifier.showNotification("", NSLocalizedString("UNKNOWN_ERROR_DB", comment: ""), .warning)
}
}
}
return nil
}
CriteresVal is an RLMObject that is composed of this:
#objcMembers
public class CriteresVal: RLMObject {
dynamic var cvalId: String?
dynamic var type: String?
dynamic var text: String?
dynamic var compositeKey: String?
override public class func primaryKey() -> String {
return "compositeKey"
}
private func updatePrimaryKey() {
self.compositeKey = "\(self.cvalId ?? "")/\(self.type ?? "")"
}
required init(_ cvalue: [AnyHashable: Any]?, _ type: String) {
super.init()
if let values = cvalue {
if let cvalId = values["id"] as? String {
self.cvalId = cvalId
} else if let cvalId = values["id"] as? Int {
self.cvalId = "\(cvalId)"
}
self.type = type
if let text = values["text"] as? String {
self.text = text
}
}
updatePrimaryKey()
}
func generateDico() -> [String: Any] {
var dicoSortie = [String: Any]()
if let realm = self.realm {
realm.refresh()
}
if let value = cvalId {
dicoSortie["id"] = value
}
if let value = type {
dicoSortie["type"] = value
}
if let value = text {
dicoSortie["text"] = value
}
return dicoSortie
}
}
compositeKey is the primary key which included cvalId and type
Thanks for help.

swift - UserDefaults wrapper

I want to make userDefaults easier to use. I write this code.
But I don't want to create a allKeys enum(cases voipToken, userId, userName, displayName,...) as the key path for my property.
If I want add a var accessToken to struct LoginInfo, I must add a case accessToken to enum AllKeys. I use CoreStore.key(.accessToken) get a keyPath for CoreStore.LoginInfo.accessToken.
import Foundation
struct CoreStore {
// static let sharedInstance = CoreStore()
// private init(){}
private static let keyPrefix = "_shared_store_"
enum allKeys: String {
case accessToken
}
static func key(_ key: allKeys) -> String {
return keyPrefix + key.rawValue
}
struct LoginInfo: CoreStoreSettable,CoreStoreGettable { }
}
extension CoreStore.LoginInfo {
static var accessToken: String? {
set {
set(value: newValue, forKey: CoreStore.key(.accessToken))
}
get {
return getString(forKey: CoreStore.key(.accessToken))
}
}
// ...
}
protocol CoreStoreSettable {}
extension CoreStoreSettable {
static func set(value: String?, forKey key: String) {
UserDefaults.standard.set(value, forKey: key)
}
}
protocol CoreStoreGettable {}
extension CoreStoreGettable {
static func getString(forKey key: String) -> String? {
return UserDefaults.standard.string(forKey: key)
}
}
Questions:
Is there a way to remove "enum allKeys"?
Should I use sharedInstance? Why?
I tried :
static var accessToken: String? {
set {
let keyPath = \CoreStore.Loginfo.accessToken
let keyPathString = keyPath.string
set(value: newValue, forKey: keyPathString)
}
get {
return getString(forKey: CoreStore.key(.accessToken))
}
}
I get error "Type of expression is ambiguous without more context"