When passing around strings or integers as IDs, the compiler doesn't tell you if you pass a person ID where a blog post ID. Now with the UniquelyTypedID
, the compiler will not let you pass the ID of a different model.
And with the power of ExpressibleByStringLiteral
and ExpressibleByIntegerLiteral
, very little code will need to change.
I've used this approach for typesafety of record IDs in my own projects, usually by manually copy-pasting. Now with Swift Macros in Swift 5.9, it's going to be so much easier.
In Package.swift, add the package to your dependencies.
.package(url: "https://github.com/FullQueueDeveloper/UniquelyTypedID.git", from: "1.0.0"),
And add "UniquelyTypedID"
to the list of your target's dependencies.
When prompted by Xcode, trust the macro.
If you had a integer ID
struct BlogPost {
let id: Int
let text: String
}
then you can use this macro instead.
struct BlogPost {
@UniquelyTypedId(Int.self) let id: ID
let text: String
}
Where ID
is the name you desire for your ID type. I tend to use simply ID
, so that I can easily reference the type with BlogPost.ID
Currently, Int
, String
, and UUID
are supported. The macro defaults to UUID
if no type is specified.
All generated IDs are Hashable
.
All generated IDs are Codable
, and they encode as single value containers, for maximum compatibility when upgrading from plain integer or string IDs.
String ID types generated by UniquelyTypedId
are expressible by string literal. This makes hardcoded data entry easier.
struct Vegetable {
@UniquelyTypedId(String.self) let scientificName: ScientificName
let name: String
}
let chili = Vegetable(scientificName: "Capsicum annuum", name: "Chili")
And if we encode this chili
to JSON we see
{
"name" : "Chili",
"scientificName" : "Capsicum annuum"
}
Integer ID types generated by UniquelyTypedId
are expressible by string literal. This makes hardcoded data entry easier.
For example
struct Aubergine: Codable {
@UniquelyTypedID(Int.self) let id: ID
let name: String
}
let aubergineID: Aubergine.ID = 3
let aubergine = Aubergine(id: 9, name: "Fairy Tale")
Introduced at WWDC '23, requiring Swift 5.9