Swift 4中使用带Self的协议模拟存在类型的实现方案问询
在Swift 4中处理带Self约束协议的集合存储问题
你遇到的是Swift里很常见的一类场景:带Self关联约束的协议无法直接作为存在类型使用。因为Bla协议里的compare(other: Self)方法要求参数必须和调用者是完全相同的类型,Swift编译器没法为这类协议生成通用的存在类型(也就是不能直接声明[Bla]数组)——它无法在运行时保证调用compare时的类型匹配,所以直接写var array: [Bla]!会编译失败。
下面给你两种Swift 4下可行的解决方案:
方案一:类型擦除(Type Erasure)
这是Swift处理这类问题的标准方案,我们可以创建一个类型擦除的包装器AnyBla,把遵循Bla的实例的行为封装起来,同时隐藏具体的类型信息:
import UIKit protocol Bla { func compare(other: Self) } extension CGFloat : Bla { func compare(other: CGFloat) { print("Extended CGFloat") } } extension UIEdgeInsets : Bla { func compare(other: UIEdgeInsets) { print("Extended UIEdgeInsets") } } // 类型擦除包装器 struct AnyBla { private let _compare: (Any) -> Void init<T: Bla>(_ value: T) { // 捕获具体类型的compare方法,添加类型检查保证安全 _compare = { other in guard let typedOther = other as? T else { print("类型不匹配,无法执行比较") return } value.compare(other: typedOther) } } // 对外暴露统一的compare方法 func compare(other: Any) { _compare(other) } } // 现在可以创建存储AnyBla的数组了 class Success { var array: [AnyBla] = [] } // 测试代码 let list = Success() let f: CGFloat = 1.23 let edge = UIEdgeInsets() list.array.append(AnyBla(f)) list.array.append(AnyBla(edge)) // 验证功能 list.array[0].compare(other: CGFloat(4.56)) // 输出 "Extended CGFloat" list.array[0].compare(other: edge) // 输出 "类型不匹配,无法执行比较" list.array[1].compare(other: UIEdgeInsets(top: 1, left: 2, bottom: 3, right: 4)) // 输出 "Extended UIEdgeInsets"
这种方案的好处是完整保留了Bla协议的核心逻辑,同时允许你在同一个数组中存储不同类型的实例,类型检查也能保证调用compare时的安全性。
方案二:放宽协议约束(仅业务允许时使用)
如果你的业务场景可以接受compare方法的参数不再严格限制为Self,可以修改协议的定义,把参数类型改为Bla:
import UIKit protocol Bla { func compare(other: Bla) } extension CGFloat : Bla { func compare(other: Bla) { guard let otherFloat = other as? CGFloat else { print("无法和非CGFloat类型比较") return } print("Extended CGFloat") } } extension UIEdgeInsets : Bla { func compare(other: Bla) { guard let otherEdge = other as? UIEdgeInsets else { print("无法和非UIEdgeInsets类型比较") return } print("Extended UIEdgeInsets") } } // 现在直接声明[Bla]数组即可编译通过 class Success { var array: [Bla] = [] } // 测试代码 let list = Success() let f: CGFloat = 1.23 let edge = UIEdgeInsets() list.array.append(f) list.array.append(edge)
这种方案的优势是实现简单,但会失去Self约束带来的编译期类型安全性,调用compare时需要手动做类型转换,适合业务上允许跨类型比较提示的场景。
总结
如果需要严格保留Bla协议的Self约束逻辑,类型擦除方案是Swift 4下最推荐的解决方式;如果业务可以接受放宽约束,修改协议定义会更简洁。
内容的提问来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

