So I have this code:
struct GameState: Equatable {
let position: SCNVector3
}
extension SCNVector3: @retroactive Equatable {
...
}
I have to put retroactive here to silence a compiler warning, because it's gonna conflict if Apple introduces the conformance itself in SceneKit.
I am wondering what would happen? Will that cause my existing app to crash on the new OS version that introduces the conformance?
Also when that happens, how should I write my code so that it compiles for both old OS and new OS? I don't see how it is possible to conditionally conform to a protocol based on OS version.
When Apple introduces the Equatable
conformance, it will be undefined behaviour. From the Swift Evolution proposal,
Now that this client has declared this conformance, if Foundation decides to add this conformance in a later revision, this client will fail to build. Before the client removes their conformance and rebuilds, however, their application will exhibit undefined behavior, as it is indeterminate which definition of this conformance will "win".
So given this is UB, crashing is definitely one of the things your app could do.
@retroactive
only silences the warning, and does not do anything else. It's a way for you to say "I know what I'm doing".
Even if you could "conditionally conform to a protocol based on OS version" (you cannot), this will not be very useful in general, since the later-added conformance might do something different that your app does not expect (though this is less of a problem for Equatable
), and cause unexpected behaviour anyway.
If you think the chance of SCNVector3
getting an Equatable
conformance in the future is high enough, don't retroactively conform to it. Create an Equatable
wrapper around it,
struct GameState: Equatable {
@EquatableVector3 var position: SCNVector3
// other properties...
}
@propertyWrapper
struct EquatableVector3: Equatable {
// this can also be a 'let' if you like
var wrappedValue: SCNVector3
static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.wrappedValue.x == rhs.wrappedValue.x &&
lhs.wrappedValue.y == rhs.wrappedValue.y &&
lhs.wrappedValue.z == rhs.wrappedValue.z
}
}
or manually write a ==
for GameState
.
struct GameState: Equatable {
let position: SCNVector3
// other properties...
static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.position.x == rhs.position.x &&
lhs.position.y == rhs.position.y &&
lhs.position.z == rhs.position.z //&&
// lhs.anotherProperty == rhs.anotherProperty
// and so on...
}
}
Perhaps in the future there are ways to declare multiple conformances to protocols and to choose which conformance to use at the call site.