【问题标题】:Method in non-final class must return `Self` to conform to protocol非最终类中的方法必须返回 `Self` 以符合协议
【发布时间】:2015-12-15 13:01:32
【问题描述】:

在协议扩展中实现返回Self的静态协议函数时,在扩展中函数的实现出现错误(没有上下文显示的最小简化场景):

import Foundation

protocol P {
    static func f() -> Self
    static func g() -> Self
}

extension P {
    static func f() -> Self { // Method 'f()' in non-final class 'NSData' must return `Self` to conform to protocol 'P'
        return g()
    }
}

extension NSData: P {
    static func g() -> Self {
        return self.init()
    }
}

在发生错误的行上将Self 替换为P 会导致编译器出现段错误(sig 11)(这似乎是传达类型不匹配错误的有效方式)。

f() 的声明更改为返回P,以及在错误行将Self 替换为P,会导致编译成功,但是会丢失类型精度(并且需要在每个调用站点强制向下转换,并详细记录Self 的要求)。

对于此问题是否有任何其他不丢失通用返回类型的解决方法?

编辑:弥补上下文不足的更多细节:P 是一个公共协议,将由库公开,以符合各种类型(并覆盖 g()),所以在NSData 中覆盖f() 不是一种选择。最好不必将f() 更改为协议扩展以外的其他内容,因为库内部在许多地方都使用了它。鉴于这两个选项,将f() 的返回类型更改为P 是更好的选择。

更新

从 Swift 4(可能是 3)开始,上述代码按原样运行。

【问题讨论】:

  • “导致编译器出现段错误(sig 11)(这似乎是传达类型不匹配错误的有效方式)。”请务必向 Apple 报告。

标签: swift interface swift2 protocol-extension


【解决方案1】:

在 Swift 3 或 4 中:

import Foundation

protocol P {
    static func f() -> Self
    static func g() -> Self
}

extension P {
    static func f() -> Self {
        return g()
    }
}

extension Data: P {
    static func g() -> Data {
        return self.init()
    }
}

或者你可以用最终的子类替换你的类:

import Foundation

protocol P {
    static func f() -> Self
    static func g() -> Self
}

extension P {
    static func f() -> Self {
        return g()
    }
}

import Foundation

final class MyData: NSData {}
extension MyData: P {
    static func g() -> Self {
        return self.init()
    }
}

如果 NSData 是那些不容易被子类化的类集群之一(您将看到带有 __CFRequireConcreteImplementation 的堆栈跟踪),您可能必须为真正的 NSData 创建最终类包装器,而不是使用子类。

【讨论】:

  • 我解决了我的问题,让我的班级 final 如你所说。谢谢!
  • 你确定你已经测试过了?但它在 Swift 3 中不起作用。它仍然抱怨Method 'g()' in non-final class 'Data' must return Self`遵守协议'P'。`
  • @OzgurVatansever 我不记得了,但可能我记得。我不再拥有 Swift 3,但我进行了编辑以包含在 Swift 4 中编译的完整示例。
  • 感谢您的回答@Jano。在我的情况下使课程最终有效,但我想了解为什么会这样?我的意思是为什么最终类解决了这个问题,而非最终类给出了这个错误?
【解决方案2】:

您需要在 NSData 扩展中覆盖 f()。

基本问题是(我认为)编译器在协议扩展中编译f 时不知道Self 是什么,我认为它假定它必须是它所应用的类的确切类型它也。对于 NSData,情况可能并非如此,因为您可能拥有它的子类。

【讨论】:

  • 对不起,如果我不清楚;不幸的是,在NSData 中覆盖f 不是一种选择,因为这只是许多符合要求的类的一个例子,其中一些是我无法控制的。我已经用更多细节更新了这个问题。似乎我必须等待 Swift 3.0 才能在没有解决方法的情况下实现这一点......
【解决方案3】:

这对我有用....

protocol P {
    static func foo()->Self
}

class C {
    required init() {}
}
extension C: P {
    static func foo() -> Self {
        return self.init()
    }
}

let c = C()
let c2 = C.foo()
print(c.dynamicType, c2.dynamicType) // C C

好的,我看到你注意到了,所以我做了更新

protocol P {
    static func foo()->Self
    static func bar()->Self
}
extension P {
    static func foo() -> Self { // Method 'f()' in non-final class 'NSData' must return `Self` to conform to protocol 'P'
        return bar()
    }
}
// the class C must be final ...
final class C {
}
// otherwise the compiler can not decide the type of Self in 
extension C: P {
    static func bar() -> Self {
        return self.init()
    }
}

let c = C()
let c2 = C.foo()
print(c.dynamicType, c2.dynamicType) // C C

对于 NSData,如果你想做同样的事情,麻烦的是把 NSData 声明为 final。

【讨论】:

  • 这对我有什么帮助?我提到我提供的示例是简化的——我不能只删除真实的函数g
【解决方案4】:

从 Swift 2.1 开始,我能够通过仅让结构(或“最终”类)符合协议来避免给定错误。目前,您可以将协议实现限制为引用类型(“仅限类”),但我还没有看到值类型的类似限制。

不过,在这种特殊情况下,我会说委托模式会适合。如果合适,您可以将 f 的默认实现移动到协议扩展中,并让子类实现覆盖委托属性。

protocol P {
    static var delegate : (() -> Self)?;
}

extension P {
    static func f() -> Self {
        // Use delegate if available, or use default implementation
    }
}

extension NSData : P {
    static var delegate : (() -> NSData)? = subImplementation;

    func subImplementation() -> NSData {
        // Some NSData-specific implementation
    }
}

【讨论】:

    【解决方案5】:

    您也可以通过使用关联类型来绕过它。

    protocol P {
        associatedtype Entity
        static func f() -> Entity
        static func g() -> Entity
    }
    
    extension P {
        static func f() -> Entity {
            return g()
        }
    }
    
    extension Data: P {
        static func g() -> Data {
            return self.init()
        }
    }
    

    您无需在实现中指定Entity,因为编译器会根据您的返回类型推断它。

    【讨论】:

      【解决方案6】:

      我也遇到了同样的问题,你解决了吗?

      这是我处理特定情况的另一种方法。 与其使用需要返回 Self 的静态函数的协议,不如考虑定义一个 Initializer 所需的协议。

      像这样:

      protocol SomeProtocol {
          init(someParameter: Int)
      }
      

      不要忘记用 required 关键字标记初始化器实现。

      class SomeClass: SomeProtocol {
          required init(someParameter: Int) {
      
          }
      }
      

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多