Hi, I’m trying to create a cache in Swift based on a dictionary with String
keys and Any
values. This is my code:
//
// Cache.swift
// GoalPilot
//
// Created by Rune Pollet on 22/06/2024.
//
import Foundation
import SwiftData
@Model
final class Cache {
private var cache = CodableDictionary()
init() {}
subscript(key: String) -> (any Codable)? {
get { cache[key] }
set { cache[key] = newValue }
}
}
// Fetch support
extension Cache {
@Transient
static func get(from context: ModelContext) -> PersistentCache {
var descriptor = FetchDescriptor<PersistentCache>()
descriptor.fetchLimit = 1
let cache = try? context.fetch(descriptor).first
guard let cache else {
let newCache = PersistentCache()
context.insert(newCache)
return newCache
}
return cache
}
}
// Dictionary wrapper to make it codable
struct CodableDictionary: Codable {
private var dictionary: [String : AnyCodable] = [:]
init(_ dictionary: [String : AnyCodable] = [:]) {
self.dictionary = dictionary
}
init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let dictionary = try container.decode([String : AnyCodable].self)
self.dictionary = dictionary
}
func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.dictionary)
}
subscript(key: String) -> (any Codable)? {
get { dictionary[key]?.value }
set { dictionary[key] = AnyCodable(newValue) }
}
}
// Wrapper to make Any conform to codable
struct AnyCodable: Codable {
enum AnyCodableError: Error {
case unsupportedType
}
enum PossibleValue: Int {
case string = 1, int, double, float, bool, array, dict
var type: any Codable.Type {
switch self {
case .string:
return String.self
case .int:
return Int.self
case .double:
return Double.self
case .float:
return CGFloat.self
case .bool:
return Bool.self
case .array:
return [AnyCodable].self
case .dict:
return [String : AnyCodable].self
}
}
static func get(from codableType: any Codable) -> PossibleValue? {
switch codableType {
case is String:
return .string
case is Int:
return .int
case is Double:
return .double
case is CGFloat:
return .float
case is Bool:
return .bool
case is [PossibleValue]:
return .array
case is [String : AnyCodableError]:
return .dict
default:
return nil
}
}
}
var value: any Codable
var valueType: PossibleValue
init?(_ value: (any Codable)?) {
if let value, let valueType = PossibleValue.get(from: value) {
self.value = value
self.valueType = valueType
} else {
return nil
}
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// Fetch the type of the value
let rawValue = try container.decode(Int.self)
let type = PossibleValue(rawValue: rawValue)
guard let type else { throw AnyCodableError.unsupportedType }
self.valueType = type
// Fetch the value
let value = try container.decode(type.type.self)
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(valueType.rawValue)
try container.encode(value)
}
}
I have no compile errors, however when using the cache in my app, I get an EXC_BREAKPOINT
error on this line of code in the init(from decoder: Decoder)
initializer of AnyCodable
:
let rawValue = try container.decode(Int.self)
I don’t know why this happens or how I could fix this, all help is welcome!
-Rune