This question is somewhat adjacent to Using subtypes in the implementation of protocol properties and function return values.
It has been a regular occurence in my experience when importing a external library, I want to encapsulate the implementation by defining a high-level local Swift API in a protocol, that I can design my application against, and then extend the imported library to conform to this API. The objective of this approach is to make it easy to swap out the imported library for another, as well as to minimize the exposure of the entire application to future API changes within the library.
protocol Avatar {
var image: URL { get }
}
protocol Project {
var avatar: Avatar { get }
}
I can now supply the implementation of this API through an imported library by extending the imported types:
// library
class ImportedAvatar { var imageURL: String {} }
class ImportedProject { var avatar: ImportedAvatar {} }
// local
extension ImportedAvatar: Avatar {
var image: URL { URL(string: self.imageURL)! }
}
extension ImportedProject: Project {} // error
In this scenario, ImportedAvatar
does not have a image: URL
but it does have a imageURL: String
, and the job of the bridging extension is to translate the foreign API into the local API. ImportedProject
already has an avatar
, which is the ImportedAvatar
.
Unfortunately, due to a limitation in the Swift compiler, type 'ImportedProject' does not conform to protocol 'Project'
, since the remote member avatar: ImportedAvatar
is not identical to the protocol requirement avatar: Avatar
. As we can see, however, ImportedAvatar
does conform to Avatar
, so technically the requirement of the Project
protocol is met.
In fact, if the foreign avatar member had a different name, this problem could be trivially solved with:
extension ImportedProject: Project {
var avatar: Avatar { self.importedAvatar }
}
Furthermore, if we had control over the imported library, this would be perfectly legal:
class ImportedProject: Project {
override var avatar: ImportedAvatar { ... }
}
Alas - the compiler is unwilling to perform this short-cut for us. What's more, it would appear to be impossible for us to perform this type translation ourselves in the event that the foreign type's member name is the same as our protocol member name, since self.avatar
is naturally recursive and does not reference the foreign avatar member:
extension ImportedProject: Project {
var avatar: Avatar { self.avatar }
}
I'd like to source ideas and suggestions on this subject. In my personal opinion, this is a language quirk that ought to be ironed out, since from a user perspective, the compiler ought to perform type upcasting automatically to conform the imported member to the protocol:
extension ImportedProject: Project {}
I can recognize that there may be reasons why the implementation does not or cannot currently behave this way, but in this case it would be important to have viable work-arounds in place and I'm hoping to use this thread to source both suggestions for resolving the underlying issue as well as suggestions for alleviating the language blockers through other means.
One work-around could be to use an associated type in the Project
protocol. The down-side of this is that this now makes the Project
protocol effectively unusable as an existential as well as that it causes all values to become concretely typed, negating the encapsulation and erasure that was gained from the outset.
Another work-around might be a means of referencing the original member of the extended type which is being shadowed by the protocol member, akin to:
extension ImportedProject: Project {
var avatar: Avatar { super.avatar }
}