0

I'm trying to understand how to filter objects that conform to a generic protocol.
Let's assume I have this set up (which compiles perfectly):

public protocol StoryItem {
    var id: Int64? { get }
}

public protocol Restorable: AnyObject {
    associatedtype T : StoryItem
    var storyItem: T? { get set }
}

public struct LabelItem: StoryItem {
    public var id: Int64?
    public var text: String?
}

public struct StickerItem: StoryItem {
    public var id: Int64?
    public var imageName: String?
}

class LabelView: UILabel, Restorable {
    var storyItem: LabelItem?
}

class StickerView: UIImageView, Restorable {
    var storyItem: StickerItem?
}

And assuming I have a UIView with multiple subviews that some of them conform to Restorable and some not, and I want to get only the subviews which conform to Restorable, I'll usually do this:

let restorableSubviews = superview.subviews.compactMap({$0 as? (Restorable & UIView)})

But I'm getting the following compile error:

Protocol 'Restorable' can only be used as a generic constraint because it has Self or associated type requirements

I read many answers in SOF but couldn't work around this one. How do I make the compiler respect my generic-protocol as a type, and get only the relevant subviews?

Roi Mulia
  • 5,626
  • 11
  • 54
  • 105
  • Unfortunately you're running up against a current limitation of Swift's generic system. One (not particularly satisfactory) option is to introduce a "type erased" parent protocol, see https://stackoverflow.com/q/40387960/2976878. – Hamish Jun 29 '19 at 13:08
  • Hey @Hamish, thank you for replying. I'm trying to understand if in your provided sample the StoryItem object should be in the "special protocol" or in the regular place where it is now? – Roi Mulia Jun 29 '19 at 13:18
  • @Hamish Just to explain how we got here: it started with https://stackoverflow.com/questions/56814387/swift-conform-to-protocol-subclass. After a great deal of back-and-forth I established that his Restorable's `storyItem` was supposed to be declared as by adopters as some StoryItem adopter (not as a literal StoryItem). That's an associated type. So this turned Restorable into a PAT and here we are. It may be that either that question or this one is an x-y question; I've no idea what the overall purpose is here. – matt Jun 29 '19 at 13:39
  • @RoiMulia Applied to your example, you'd want it to look something like this: https://gist.github.com/hamishknight/0e7c214fc2d9d05f955a926280c122c2. Also note that if `Restorable` can only be conformed to by `UIView` subclasses, you can replace `: AnyObject` with `: UIView` (assuming you're using Swift 5), which would allow you to cast to just `TypeErasedRestorable`. – Hamish Jun 29 '19 at 16:27
  • @matt Yeah, as currently stated (especially with the property requirement being specified as `get set`) I don't see an obvious way of avoiding the use of a PAT. With some more context (such as what `restorableSubviews` is used for, why the requirement needs to be `get set`) it might be possible to re-design things to not use a PAT. – Hamish Jun 29 '19 at 16:34
  • @Hamish Thank you so much for the gist! One small question, when implementing it I'm trying to understand what is the `storyItem` inner property here: `var typeErasedStoryItem: StoryItem? { storyItem }`. As the compiler treat that as an unresolved variable. – Roi Mulia Jun 29 '19 at 16:59
  • @RoiMulia Ah sorry, that’s Swift 5.1 syntax. Pre 5.1 you’d want `{ return storyItem }` instead of `{ storyItem }`. Its purpose is to provide a default implementation of `typeErasedStoryItem` such that adopting types don’t need to define it themselves. – Hamish Jun 29 '19 at 17:44
  • @Hamish Thank you for your help and patience! It worked but I don't have access to Restorable properties ah. I see that eventually, I'm more constraining myself than fixing stuff, I'm considering switching the entire design ah – Roi Mulia Jun 30 '19 at 10:36

0 Answers0