Swift:如何过滤符合带关联类型泛型协议的视图对象?
如何筛选符合带关联类型泛型协议的视图?
首先还原你的代码场景,方便理解问题:
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? }
你尝试用这段代码筛选子视图时遇到了编译错误:
Protocol 'Restorable' can only be used as a generic constraint because it has Self or associated type requirements
为什么会报错?
原因很直白:带关联类型(associatedtype)的协议属于泛型协议,Swift编译器没法直接把它当作具体类型使用——它不知道associatedtype T到底对应哪个StoryItem的具体实现,自然无法完成类型转换。
解决方案:用非泛型基础协议统一筛选
这里推荐最实用的方法:定义一个不带关联类型的基础协议,让Restorable继承它,通过这个基础协议来统一筛选视图。
步骤1:添加非泛型的基础协议
先创建AnyRestorable协议,它只暴露一个通用的StoryItem类型属性,不需要关联类型:
public protocol AnyRestorable: UIView { // 定义通用属性,用于统一访问所有Restorable视图的storyItem var anyStoryItem: StoryItem? { get } }
步骤2:让Restorable继承基础协议并添加默认实现
修改Restorable协议,让它继承AnyRestorable,再通过扩展提供anyStoryItem的默认实现:
public protocol Restorable: AnyRestorable, AnyObject { associatedtype T : StoryItem var storyItem: T? { get set } } // 给Restorable添加默认实现,自动将具体的storyItem转为通用的StoryItem类型 extension Restorable { public var anyStoryItem: StoryItem? { return storyItem } }
步骤3:用基础协议筛选视图
现在你就能正常筛选所有实现了Restorable的子视图了:
let restorableSubviews = superview.subviews.compactMap { $0 as? AnyRestorable }
后续访问具体类型的storyItem
如果需要访问某个视图的具体storyItem类型,再做一次类型转换即可:
for view in restorableSubviews { if let labelView = view as? LabelView { print(labelView.storyItem?.text) } else if let stickerView = view as? StickerView { print(stickerView.storyItem?.imageName) } }
备选思路:类型擦除包装器
如果你不想修改原有协议结构,也可以创建一个类型擦除的包装器AnyRestorableView:
class AnyRestorableView: UIView, AnyRestorable { private let _anyStoryItem: () -> StoryItem? init<R: Restorable>(_ restorable: R) where R: UIView { self._anyStoryItem = { restorable.storyItem } super.init(frame: restorable.frame) // 如需保留原视图的其他属性,可在此处处理 } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } var anyStoryItem: StoryItem? { return _anyStoryItem() } }
不过这种方法相对繁琐,除非有特殊需求,否则还是推荐第一种基础协议的方案。
内容的提问来源于stack exchange,提问作者Roi Mulia
相关产品推荐
相关产品推荐

