I want to define a set of types like this:
protocol UserProfile {
var userId: String? { get set }
// ...
}
// UserProfile also has several extensions
protocol AvatarImageCache: NSObject {
var imageURL: String? { get set }
var imageLoadError: Bool { get set }
var image: Image? { get set }
}
protocol UserProfileWithAvatar: UserProfile, AvatarImageCache, Identifiable where ID == String {
var id: String { get }
}
extension UserProfileWithAvatar {
var id: String { userId ?? "" }
}
struct GroupMember {
let user: UserProfileWithAvatar // error here
let addedAt: Date
}
This gives the dreaded error, Protocol 'UserProfileWithAvatar' can only be used as a generic constraint because it has Self or associated type requirements
. If I remove the Identifiable
clause from UserProfileWithAvatar, that error goes away, so it seems like Identifiable
is the source of the problem. But why? I've declared the type of ID in the way XCode recommends, so what's the problem? If I need to redeclare it in the struct, how? Adding a similar where clause there gives another error.
There's more. I also tried moving the Identifiable
conformance/inheritance clause to the extension:
extension UserProfileWithAvatar: Identifiable {
typealias ID = String
var id: String { userId ?? "" }
}
For some reason I can't use where ID == String
in this context, but even after fixing that error, I get a new one: Extension of protocol 'UserProfileWithAvatar' cannot have an inheritance clause
. Declaring conformance to other protocols in extensions is usually allowed, so what's special about this one? I've also tried removing AvatarImageCache
in case its inheritance of NSObject
was clashing with some undocumented part of Identifiable
, but that didn't change these errors either.
Perhaps I don't even need to declare conformance to Identifiable
anyway? I want it so I can display a SwiftUI ForEach
view that iterates over a collection of UserProfileWithAvatar
(FWIW the protocol is implemented by two classes, one for the local user and one for remote users). ForEach
requires that elements conform to Identifiable
. 'Class types' have a default implementation of Identifiable but even that's slightly ambiguous. Do they mean any object instances, or only instances of SomeClass.self? If it's all class objects, that should work for me, because AvatarImageCache
forces conformance with NSObject
(for the sake of mutability), but I doubt it will be that easy, because Swift always seems to throw obstructions in my path.
If the previous paragraph doesn't hold the solution, I suppose I could move the Identifiable
to struct GroupMember
, but I'd rather keep that as a private implementation detail.