I’m implementing a changelog of sorts for when my app syncs with iCloud. Types that conform to Recordable
can be “recorded” in this changelog and “replayed” later on other devices. Each record is stored in the changelog as a JSON-encoded Data
object.
In order to avoid switching on the record type at each use all over the place, I wanted to implement a convenience method that other parts can call to have stuff done on a record – without having to duplicate the switching (which is likely to be expanded with more types) and decoding.
I don’t know how much sense this makes; I’m probably missing central concepts in all this. It's illustrated in the much simplified Playground below.
My question are:
- What do the errors mean?
- Why can I use
r.name
, but notr.id
? - What’s needed to make this work (what am I doing wrong)?
Same errors occur if I make a computed Recordable
value on Change
that does the decoding.
Thanks
import Foundation
protocol Recordable: Codable & Identifiable where ID == Int64 {
var name: String { get set }
}
struct Game: Recordable {
var id: Int64
var name: String
var players: Array<Player>
}
struct Player: Recordable {
var id: Int64
var name: String
}
struct Change {
let recordKind: String // The type name basically
let recordData: Data // The record as a data object
let operation: String // Insert, Update, or Delete
init<R: Recordable>(record: R, recordKind: String, operation: String) throws {
self.recordKind = recordKind
self.recordData = try JSONEncoder().encode(record)
self.operation = operation
}
/// Applies the closure on the `Recordable` record, encoded in our `recordData`.
func apply(_ applying: (Recordable) -> Void) throws {
let decoder = JSONDecoder()
switch self.recordKind {
case "Game":
let game = try decoder.decode(Game.self, from: self.recordData)
applying(game)
case "Player":
let player = try decoder.decode(Player.self, from: self.recordData)
applying(player)
case let kind:
fatalError("Unknown Record Kind: \(kind)")
}
}
}
let player = Player(id: 1, name: "The player")
let game = Game(id: 2, name: "The game", players: [player])
// Record, print changes...
let changes = [
try! Change(record: player, recordKind: "Player", operation: "Insert"),
try! Change(record: game, recordKind: "Game", operation: "Insert"),
try! Change(record: player, recordKind: "Player", operation: "Delete"),
]
for change in changes {
try change.apply { (r: Recordable) in
print("This prints fine: \(r.name), \(r)")
let id = r.id // Error: Property 'id' requires that 'Recordable' be a class type
print(r.id) // Error: Member 'id' cannot be used on value of protocol type 'Recordable'; use a generic constraint instead
}
}
$ swiftc --version
swift-driver version: 1.44.2 Apple Swift version 5.6 (swiftlang-5.6.0.320.8 clang-1316.0.18.8)