Swift中非final类遵守含Self的协议报错,空协议扩展为何可行?
我想要实现一个仅当新值与当前值不同时才更新属性的方法,先定义了如下示例模型:
class Model { var id: Int = 0 } class Person: Model { var name: String = "" } class Dog: Model { var color: UIColor = .black }
方案一:带协议要求的Updatable(编译报错)
首先定义包含方法要求的Updatable协议,并提供默认实现:
协议定义
protocol Updatable: AnyObject{ @discardableResult func update<Value: Equatable>( _ keyPath: ReferenceWritableKeyPath<Self, Value>, to value: Value ) -> Bool }
默认实现
extension Updatable { @discardableResult func update<Value: Equatable>( _ keyPath: ReferenceWritableKeyPath<Self, Value>, to value: Value ) -> Bool { guard self[keyPath: keyPath] != value else { return false } self[keyPath: keyPath] = value return true } }
当让非final类Model遵守该协议时:
extension Model: Updatable {}
编译器抛出错误:
Protocol 'Updatable' requirement 'update(_:to:)' cannot be satisfied by a non-final class ('Model') because it uses 'Self' in a non-parameter, non-result type position
方案二:空协议+扩展方法的Updatable2(正常运行)
将协议改为空协议,在扩展中添加同名逻辑的方法:
协议与扩展实现
protocol Updatable2: AnyObject { } extension Updatable2 { @discardableResult func update2<Value: Equatable>( _ keyPath: ReferenceWritableKeyPath<Self, Value>, to value: Value ) -> Bool { print("Default: \(keyPath) -> \(value)") guard self[keyPath: keyPath] != value else { return false } self[keyPath: keyPath] = value return true } } extension Model: Updatable2 {}
以下调用均能正常运行:
let model = Model() model.update2(\.id, to: 200) let person = Person() person.update2(\.name, to: "Sally") person.update2(\.id, to: 400) let car = Car() car.update2(\.wheels, to: 5) let models: [Model] = [ model, person, car, ] models.forEach { $0.update2(\.id, to: 666) }
差异原因解析
核心区别在于方法是否属于协议的强制要求:
协议要求的方法:
当方法是协议的必须实现项时,编译器需要保证该方法在所有子类中能被正确重写和兼容。这里的ReferenceWritableKeyPath<Self, Value>中,Self作为泛型参数的约束类型(键路径的根类型),对于非final类来说,子类的Self是子类类型,父类的Self是父类类型,编译器无法确保子类重写的方法能满足父类的协议要求——这种Self出现在非参数/非返回值的泛型约束位置的情况,会导致继承时的类型冲突,因此编译器禁止非final类遵守此类协议。协议扩展中的默认方法:
这种方法不属于协议的强制要求,只是给遵守协议的类型提供一个“附加”的实现,采用静态派发逻辑。调用时Self会绑定到具体的调用类型(比如Person或Model),编译器不需要处理子类重写的兼容性问题——即使子类继承该方法,也不会触发协议要求的类型校验,因此不会报错,且能正常工作。
内容的提问来源于stack exchange,提问作者Rob C

