I'm not sure if this is even possible, but I thought I should ask anyway!
I have a "UserDefaults" client and "live"/"mock" implementations of its interface (taken from a TCA example, but that shouldn't matter).
I want to have the mock implementation act like the live one (so that it should be possible to read/write values), but without actually persisting.
Here's my code (ready for a Playground):
import Foundation
import PlaygroundSupport
// Interface - this can't change
public struct UserDefaultsClient {
public var boolForKey: @Sendable (String) -> Bool
public var setBool: @Sendable (Bool, String) async -> Void
}
// Live implementation (ignore if you want, just for reference)
extension UserDefaultsClient {
public static let live: Self = {
let defaults = { UserDefaults(suiteName: "group.testing")! }
return Self(
boolForKey: { defaults().bool(forKey: $0) },
setBool: { defaults().set($0, forKey: $1) }
)
}()
}
// Mock implementation
extension UserDefaultsClient {
static let mock: Self = {
let storage = Storage()
return Self(
boolForKey: { key in
// ❌ Compiler error: Cannot pass function of type '@Sendable (String) async -> Bool' to parameter expecting synchronous function type
return await storage.getBool(for: key)
// return false
},
setBool: { boolValue, key in
await storage.setBool(boolValue, for: key)
}
)
}()
}
// Manages a storage of Bools, mapped to a key
actor Storage {
var storage: [String: Bool] = [:]
func setBool(_ bool: Bool, for key: String) async {
storage[key] = bool
}
func getBool(for key: String) -> Bool {
storage[key] ?? false
}
}
let client: UserDefaultsClient = .live
Task {
client.boolForKey("key")
await client.setBool(true, "key")
client.boolForKey("key")
}
PlaygroundPage.current.needsIndefiniteExecution = true
I tried using a Task
when calling storage.getBool
, but that didn't work either.
Any ideas?