swift protocol conformance when same property name is optional

Viewed 888

When a protocol declares a property as optional and a concrete type declares it as non-optional, how can I make the concrete type conform to the protocol?

Here's the problem:

protocol Track {
    var trackNumber: Int? { get } // not all tracks have a track number
}

struct SpotifyTrack {
    let trackNumber: Int // all SpotifyTrack are guaranteed to have a track number
}

extension SpotifyTrack: Track {
    var trackNumber: Int? {
        return self.trackNumber // WARNING: All paths through this function will call itself
    }
}

I don't want to make trackNumber optional in SpotifyTrack because I know the value will always be there for SpotifyTracks. Are there are any solutions more elegant than just renaming the properties?

3 Answers

There is no elegant solution to your problem other than renaming the conflicting property on the conforming type.

Swift doesn't allow 2 properties of the same name to exist on a type even if their types are different. On the other hand, Int? and Int are completely different types, so you cannot have trackNumber: Int fulfil the protocol requirement of trackNumber: Int?.

The only solution (other than changing the type in either the protocol or the struct) is to rename the non-Optional property in SpotifyTrack and make an optional computed property of the same name returning the non-optional one.

protocol Track {
    var trackNumber: Int? { get }
}
struct SpotifyTrack {
    private let _trackNumber: Int
}
extension SpotifyTrack: Track {
    var trackNumber: Int? { _trackNumber }
}

You can have a second property that acts as backing for the first:

protocol Track {
    var trackNumber: Int? { get }
}
struct SpotifyTrack {
    var realTrackNumber: Int
}
extension SpotifyTrack: Track {
    var trackNumber: Int? { self.realTrackNumber }
}

However, I have a feeling that that is what you mean by "renaming": you would prefer not to do that. Nevertheless, the "instructions" of a protocol are firm: you cannot adopt Track without declaring a trackNumber that is explicitly typed as Int?.

Objective-C has @optional protocol members, meaning you could have Track declare trackNumber Int but @optional, and then you could adopt Track without having a trackNumber at all. But if you wanted to take advantage of that, this could not be a struct.

One way could be that you have a base track protocol and then have a second protocol which extend the base protocol. For example

protocol BaseTrackProtocol {
    var title:String
    var artist:String
}

protocol SpotifyTrackProtocol : BaseTrackProtocol {
    var trackNumber:Int
}

struct SoptifyTrack: SpotifyTrackProtocol {
    var title:String
    var artist:String
    var trackNumber:Int
}

other type of tracks could then follow different extensions of the base track protocol

hope it helps :)

Related