协议功能返回自我


80

我有一个协议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 Cfinalie做前缀的情况外,没有任何作用:

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

但是,如果我想继承C的子类,那么什么也行不通。有没有办法解决?


1
“什么都不起作用”是什么意思?
Rob Napier 2014年

编译器会抱怨将C或Self用作返回值,除非classa是final class
aeubanks

6
好的,我已经复制了错误,但是在提问时,您需要包括返回的实际错误。不仅仅是“它会带来错误”或“它不起作用”。
Rob Napier 2014年

编译器在这里的错误是完全正确的,顺便说一句。我只是在考虑您是否可以得到您想要做的事情。
Rob Napier 2014年

1
但是你可以打电话[[[self class] alloc] init]。所以我想问题是,有没有一种类型安全的方法来调用当前类并调用init方法?
aeubanks 2014年

Answers:


144

问题是您要保证编译器无法证明您会保留。

因此,您创建了以下承诺:调用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() -> C在以前的beta中有效,但它是一致的,因为没有继承协议一致性。(现在看来协议一致性是继承的,并且func copy() -> C不起作用。)
newacct 2014年

2
最后的纯迅速解决不符合的子类,因为它们都需要实现的工作init(copy: C),而不是init(copy: Self):(
fluidsonic

最后一个解决方案保证返回值是,Self但是初始化器都必须接受静态类型的变量C,也就是说,仅返回AnyObject一个地方并没有太大的改进。
chakrit

1
self.dynamicType.init( ... )
Swift

1
C中的@ Dschee,Self可以是C或C的子类。它们是不同的类型。
罗布·纳皮尔

24

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

protocol Copyable {
    init(copy:Self)
}

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

这是一个很好的答案,并且在WWDC 2015上对该类型的方法进行了广泛讨论
。– gkaimakas

2
这应该是公认的答案。可以使用return Self(copy: self)(至少在Swift 2.2中)进行简化。
jhrmnn '16

16

有另一种方法可以完成您想要的事情,其中​​涉及利用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()

迷人。我想知道这是否与stackoverflow.com/q/42041150/294884
Fattie

这是我感兴趣的内容。谢谢!
乔什在书呆子

10

实际上,有一个技巧可以在协议要求时轻松返回Selfgist):

/// 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

1
哇。编译。这很棘手,因为编译器不允许您这样做return Vehicle() as! Self
SimplGy 2016年

这令人难以置信。哇。我在这里问的实际上是对此的一种变化吗?stackoverflow.com/q/42041150/294884
Fattie

@JoeBlow恐怕不是。我要说的是,为了确保我们的头脑安全,我们应该确切地知道返回类型(即不是“ A或B”,而是“ A”;否则,我们必须考虑多态+继承+函数重载(至少)。)
Arediver

那是编译器的欺骗。由于foo()不强制覆盖,因此每个Vehicle没有foo()自定义实现的后代都会在中产生明显的崩溃autocast()。例如:class SuperCar: Vehicle { } let superCar = SuperCar.foo() 。的实例Vehicle无法向下转换SuperCar-因此在'autocast()'中强制展开nil会导致崩溃。
freennnn

1
@freennnn当不重写子类时,将代码更改为以下代码不会崩溃foo()。唯一的要求是,该类Foo必须具有必需的初始化程序才能使其正常工作,如下所示。 class Vehicle: Foo { public required init() { // Some init code here } class func foo() -> Self { return autocast(self.init())! // return autocast(Vehicle())! } } class Tractor: Vehicle { //Override is not necessary /*override class func foo() -> Self { return autocast(Tractor())! }*/ }
shawnynicole

2

根据Rob的建议,可以通过关联类型使它更通用。我对示例进行了一些更改,以演示该方法的好处。

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)
    }
}

1

我遇到了类似的问题,并提出了可能有用的东西,因此尽管我将其共享以备将来参考,因为这是我在寻找解决方案时发现的第一个地方。

如上所述,问题是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() }       
}

当然,我的上下文和功能是完全不同的,但是从本质上讲,我试图尽可能地接近给出的示例。


0

只是把我的帽子扔进这里。我们需要一个协议,该协议返回一个可选的协议类型。我们还希望重写显式返回类型,而不仅仅是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? {
        ...
    }
}

0

为了增加答案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()

0

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.