【问题标题】:Protocol func returning Self协议函数返回 Self
【发布时间】:2014-10-27 23:59:27
【问题描述】:

我有一个返回对象副本的协议 P:

protocol P {
    func copy() -> Self
}

还有一个实现 P 的 C 类:

class C : P {
    func copy() -> Self {
        return C()
    }
}

但是,无论我将返回值设置为Self,我都会收到以下错误:

无法将“C”类型的返回表达式转换为“Self”类型的返回

我也尝试返回C

class C : P {
    func copy() -> C  {
        return C()
    }
}

这导致了以下错误:

非最终类 'C' 中的方法 'copy()' 必须返回 Self 以符合 到协议'P'

除了我在 class C 前加上 final 的情况外,没有任何效果,即:

final class C : P {
    func copy() -> C  {
        return C()
    }
}

但是,如果我想将 C 子类化,那么什么都行不通。有没有办法解决这个问题?

【问题讨论】:

  • “没有任何效果”是什么意思?
  • 编译器在将 C 或 Self 作为返回值时会报错,除非 classfinal class
  • 好的,我已经重现了错误,但是在提问时,您需要包含返回的实际错误。不仅仅是“它给出错误”或“它不起作用”。
  • 编译器在这里的错误是完全正确的,顺便说一句。我只是在想你是否能得到你想做的事情。
  • 但您可以致电[[[self class] alloc] init]。所以我想问题是有没有一种类型安全的方法来调用当前类并调用 init 方法?

标签: swift protocols subclassing swift-protocols


【解决方案1】:

Swift 5.1 现在允许强制转换为 Self,as! Self

  1> protocol P { 
  2.     func id() -> Self 
  3. } 
  9> class D : P { 
 10.     func id() -> Self { 
 11.         return D()
 12.     } 
 13. } 
error: repl.swift:11:16: error: cannot convert return expression of type 'D' to return type 'Self'
        return D()
               ^~~
                   as! Self


  9> class D : P { 
 10.     func id() -> Self { 
 11.         return D() as! Self
 12.     } 
 13. } //works

【讨论】:

    【解决方案2】:

    根据 Rob 的建议,可以使用 associated types 使其更通用。我对示例进行了一些更改,以展示该方法的好处。

    protocol Copyable: NSCopying {
        associatedtype Prototype
        init(copy: Prototype)
        init(deepCopy: Prototype)
    }
    class C : Copyable {
        typealias Prototype = C // <-- requires adding this line to classes
        required init(copy: Prototype) {
            // Perform your copying here.
        }
        required init(deepCopy: Prototype) {
            // Perform your deep copying here.
        }
        @objc func copyWithZone(zone: NSZone) -> AnyObject {
            return Prototype(copy: self)
        }
    }
    

    【讨论】:

      【解决方案3】:

      要使用associatedtype 方式添加答案,我建议将实例的创建移至协议扩展的默认实现。这样,符合标准的类就不必实现它,从而使我们免于代码重复:

      protocol Initializable {
          init()
      }
      
      protocol Creatable: Initializable {
          associatedtype Object: Initializable = Self
          static func newInstance() -> Object
      }
      
      extension Creatable {
          static func newInstance() -> Object {
              return Object()
          }
      }
      
      class MyClass: Creatable {
          required init() {}
      }
      
      class MyOtherClass: Creatable {
          required init() {}
      }
      
      // Any class (struct, etc.) conforming to Creatable
      // can create new instances without having to implement newInstance() 
      let instance1 = MyClass.newInstance()
      let instance2 = MyOtherClass.newInstance()
      

      【讨论】:

        【解决方案4】:

        只是在这里把我的帽子扔进戒指。我们需要一个协议,该协议返回应用协议类型的可选项。我们还希望覆盖显式返回类型,而不仅仅是 Self。

        诀窍不是使用“Self”作为返回类型,而是定义一个关联类型,将其设置为等于 Self,然后使用该关联类型。

        这是旧方法,使用 Self...

        protocol Mappable{
            static func map() -> Self?
        }
        
        // Generated from Fix-it
        extension SomeSpecificClass : Mappable{
            static func map() -> Self? {
                ...
            }
        }
        

        这是使用关联类型的新方法。请注意,返回类型现在是显式的,而不是 'Self'。

        protocol Mappable{
            associatedtype ExplicitSelf = Self
            static func map() -> ExplicitSelf?
        }
        
        // Generated from Fix-it
        extension SomeSpecificClass : Mappable{
            static func map() -> SomeSpecificClass? {
                ...
            }
        }
        

        【讨论】:

        • 更新:我认为 Swift 现在可以正确地让您使用 Self 作为返回类型。编译器发生了变化。我已经有一段时间不用这样的解决方法了。
        【解决方案5】:

        还有另一种方法可以做你想做的事,它涉及利用 Swift 的关联类型。这是一个简单的例子:

        public protocol Creatable {
        
            associatedtype ObjectType = Self
        
            static func create() -> ObjectType
        }
        
        class MyClass {
        
            // Your class stuff here
        }
        
        extension MyClass: Creatable {
        
            // Define the protocol function to return class type
            static func create() -> MyClass {
        
                 // Create an instance of your class however you want
                return MyClass()
            }
        }
        
        let obj = MyClass.create()
        

        【讨论】:

        【解决方案6】:

        实际上,有一个技巧可以轻松在协议需要时返回Self (gist):

        /// Cast the argument to the infered function return type.
        func autocast<T>(some: Any) -> T? {
            return some as? T
        }
        
        protocol Foo {
            static func foo() -> Self
        }
        
        class Vehicle: Foo {
            class func foo() -> Self {
                return autocast(Vehicle())!
            }
        }
        
        class Tractor: Vehicle {
            override class func foo() -> Self {
                return autocast(Tractor())!
            }
        }
        
        func typeName(some: Any) -> String {
            return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
        }
        
        let vehicle = Vehicle.foo()
        let tractor = Tractor.foo()
        
        print(typeName(vehicle)) // Vehicle
        print(typeName(tractor)) // Tractor
        

        【讨论】:

        • 哇。编译。这很棘手,因为编译器不会让你只使用return Vehicle() as! Self
        • 这令人难以置信。哇。我在这里问的实际上是一个变化吗? stackoverflow.com/q/42041150/294884
        • @JoeBlow 恐怕不是。我想说,为了让我们的思想安全,我们应该准确地知道返回类型(即不是“A 或 B”,而只是“A”;否则我们必须考虑多态性 + 继承 + 函数重载(至少)。跨度>
        • 那是编译器欺骗。由于未强制覆盖foo(),因此每个没有foo() 自定义实现的Vehicle 后代都会在autocast() 中产生明显的崩溃。例如:class SuperCar: Vehicle { } let superCar = SuperCar.foo() Vehicle 的实例不能向下转换为 SuperCar - 所以在 'autocast()' 中强制解开 nil 会导致崩溃。
        • @freennnn 将代码更改为以下代码不会在子类未覆盖 foo() 时崩溃。唯一的要求是 Foo 类必须有一个必需的初始化程序才能使其工作,如下所示。 class Vehicle: Foo { public required init() { // Some init code here } class func foo() -&gt; Self { return autocast(self.init())! // return autocast(Vehicle())! } } class Tractor: Vehicle { //Override is not necessary /*override class func foo() -&gt; Self { return autocast(Tractor())! }*/ }
        【解决方案7】:

        使用 Swift 2,我们可以为此使用协议扩展。

        protocol Copyable {
            init(copy:Self)
        }
        
        extension Copyable {
            func copy() -> Self {
                return Self.init(copy: self)
            }
        }
        

        【讨论】:

        • 这是一个很好的答案,这种方法在 WWDC 2015 上得到了广泛的讨论。
        • 这应该是公认的答案。可以使用return Self(copy: self) 进行简化(至少在 Swift 2.2 中)。
        【解决方案8】:

        我遇到了类似的问题,并想出了一些可能有用的东西,所以我想分享它以供将来参考,因为这是我在寻找解决方案时首先找到的地方之一。

        如上所述,问题在于 copy() 函数的返回类型不明确。这可以通过将 copy() -> C 和 copy() -> P 函数分开来非常清楚地说明:

        所以,假设你定义协议和类如下:

        protocol P
        {
           func copy() -> P
        }
        
        class C:P  
        {        
           func doCopy() -> C { return C() }       
           func copy() -> C   { return doCopy() }
           func copy() -> P   { return doCopy() }       
        }
        

        当返回值的类型是明确的时,这会编译并产生预期的结果。每当编译器必须(自行)决定返回类型应该是什么时,它会发现情况模棱两可,并且对于所有实现 P 协议的具体类都会失败。

        例如:

        var aC:C = C()   // aC is of type C
        var aP:P = aC    // aP is of type P (contains an instance of C)
        
        var bC:C         // this to test assignment to a C type variable
        var bP:P         //     "       "         "      P     "    "
        
        bC = aC.copy()         // OK copy()->C is used
        
        bP = aC.copy()         // Ambiguous. 
                               // compiler could use either functions
        bP = (aC as P).copy()  // but this resolves the ambiguity.
        
        bC = aP.copy()         // Fails, obvious type incompatibility
        bP = aP.copy()         // OK copy()->P is used
        

        总之,这适用于以下情况:不使用基类的 copy() 函数,或者始终具有显式类型上下文。

        我发现使用与具体类相同的函数名称会导致到处都是笨拙的代码,因此我最终为协议的 copy() 函数使用了不同的名称。

        最终结果更像:

        protocol P
        {
           func copyAsP() -> P
        }
        
        class C:P  
        {
           func copy() -> C 
           { 
              // there usually is a lot more code around here... 
              return C() 
           }
           func copyAsP() -> P { return copy() }       
        }
        

        当然,我的上下文和功能完全不同,但本着问题的精神,我试图尽可能接近给出的示例。

        【讨论】:

          【解决方案9】:

          问题是你做出了编译器无法证明你会遵守的​​承诺。

          所以你创建了这个承诺:调用 copy() 将返回它自己的类型,完全初始化。

          但后来你以这种方式实现了copy()

          func copy() -> Self {
              return C()
          }
          

          现在我是一个不会覆盖copy() 的子类。我返回一个C,而不是一个完全初始化的Self(我承诺过)。所以这不好。怎么样:

          func copy() -> Self {
              return Self()
          }
          

          好吧,那不会编译,但即使编译了,也不好。子类可能没有简单的构造函数,因此D() 甚至可能不合法。 (虽然见下文。)

          好的,那怎么样:

          func copy() -> C {
              return C()
          }
          

          是的,但这不会返回 Self。它返回C。你还是没有兑现承诺。

          “但是 ObjC 可以做到!”嗯,有点。主要是因为它不在乎你是否像 Swift 那样信守诺言。如果您未能在子类中实现copyWithZone:,您可能无法完全初始化您的对象。编译器甚至不会警告你你已经这样做了。

          “但是 ObjC 中的大多数东西都可以翻译成 Swift,而且 ObjC 有NSCopying。”是的,它是这样定义的:

          func copy() -> AnyObject!
          

          所以你也可以这样做(这里没有 ! 的理由):

          protocol Copyable {
            func copy() -> AnyObject
          }
          

          这就是说“我不承诺你会得到什么。”你也可以说:

          protocol Copyable {
            func copy() -> Copyable
          }
          

          这是你可以做出的承诺。

          但我们可以考虑一下 C++ 并记住我们可以做出的承诺。我们可以保证我们和我们所有的子类都将实现特定类型的初始化器,而 Swift 将强制执行(因此可以证明我们说的是真的):

          protocol Copyable {
            init(copy: Self)
          }
          
          class C : Copyable {
            required init(copy: C) {
              // Perform your copying here.
            }
          }
          

          这就是您应该执行复制的方式。

          我们可以更进一步,但它使用dynamicType,我没有对其进行广泛测试以确保它始终是我们想要的,但它应该是正确的:

          protocol Copyable {
            func copy() -> Self
            init(copy: Self)
          }
          
          class C : Copyable {
            func copy() -> Self {
              return self.dynamicType(copy: self)
            }
          
            required init(copy: C) {
              // Perform your copying here.
            }
          }
          

          这里我们保证有一个初始化器为我们执行复制,然后我们可以在运行时确定调用哪个初始化器,为我们提供您正在寻找的方法语法。

          【讨论】:

          • 嗯,他们一定改变了这一点。我可以发誓func copy() -&gt; C 在以前的测试版中工作,它是一致的,因为没有继承协议一致性。 (现在看来协议一致性是继承的,func copy() -&gt; C 不起作用。)
          • 最后一个纯 Swift 解决方案不适用于子类,因为它们需要实现 init(copy: C) 而不是 init(copy: Self) :(
          • 最后一个解决方案保证返回值为Self,但是初始化器都必须接受一个静态类型为C的变量,也就是说,仅仅返回@987654346并没有太大的改进@ 首先。
          • 在 swift 2.0 中,您必须明确调用 init:self.dynamicType.init( ... )
          • @Dschee 在 C 中,Self 可以是 C 或 C 的子类。它们是不同的类型。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-29
          • 1970-01-01
          • 1970-01-01
          • 2021-05-24
          • 1970-01-01
          相关资源
          最近更新 更多