协议函数返回Self

90

我有一个名为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'中的'method copy()'必须返回Self以符合协议'P'

除了在定义'class C'时添加'final'前缀的情况外,没有其他可行的方案:

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

然而,如果我想要继承C类,则什么都行不通。有没有什么办法可以解决这个问题?


1
你说的“什么都不起作用”是什么意思? - Rob Napier
除非“类”是“final class”,否则编译器会在将C或Self作为返回值时发出警告。 - aeubanks
6
好的,我已经重现了错误,但是在提问时,您需要包括实际返回的错误,而不仅仅是“它给出错误”或“它不起作用”。 - Rob Napier
1
但是你可以调用[[[self class] alloc] init]。所以我的问题是,有没有一种类型安全的方式来调用当前类并调用init方法? - aeubanks
@aeubanks:是的,你可以使用self.dynamicType()。你调用的初始化程序必须声明为required,因为在Swift中初始化程序并不总是被继承的。 - newacct
显示剩余3条评论
9个回答

156

问题在于你做出了一个编译器无法证明你会遵守的承诺。

因此,你创建了这个承诺:调用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在之前的测试版中是有效的,并且它是一致的,因为协议符合性没有被继承。(现在似乎协议符合性被继承了,而func copy() -> C不起作用了。) - newacct
2
最后一个纯Swift的解决方案无法与子类一起使用,因为它们需要实现init(copy: C)而不是init(copy: Self) :( - fluidsonic
我认为这仍然比AnyObject有很大的改进,但确实意味着您必须能够复制您的超类(因此您的子类属性必须有默认值,或者您必须使用fatalError()或类似的东西,这很丑陋)。如果您摆脱了Copyable协议,则问题会消失。只要您实现init(copy:)而不承诺这样做(但它也不会强制您在每个层面上实现init(copy:)),则所有内容都按预期运行。请参见http://stackoverflow.com/a/28795620/97337以及https://devforums.apple.com/message/1086442。 - Rob Napier
1
在 Swift 2.0 中,您需要显式调用 init:self.dynamicType.init( ... ) - pronebird
1
在C语言中,Self可以是C或C的子类。它们是不同的类型。 - Rob Napier
显示剩余3条评论

28

有了Swift 2,我们可以使用协议扩展来实现这一点。

protocol Copyable {
    init(copy:Self)
}

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

这是一个很好的答案,这种方法在2015年的WWDC上进行了广泛讨论。 - gkaimakas
2
这应该是被接受的答案。它可以简化为 return Self(copy: self)(至少在Swift 2.2中)。 - jhrmnn

19

有另一种实现你想要的方法,它涉及利用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()

有趣。我想知道这是否与http://stackoverflow.com/q/42041150/294884有关。 - Fattie
这个正是我感兴趣的。谢谢! - Josh at The Nerdery

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
这真是令人难以置信。哇。我在这里询问的实际上是这个的变体吗?http://stackoverflow.com/q/42041150/294884 - Fattie
@JoeBlow,恐怕不是这样的。我认为为了保证我们的思维安全,我们应该确切地知道返回类型(即不是“A或B”,而只是“A”);否则,我们必须考虑多态性+继承+函数重载(至少)。 - werediver
这是编译器的技巧。由于foo()的重写没有被强制执行,每个没有自定义实现foo()Vehicle子类都会在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

5

Swift 5.1现在允许将强制转换为自身,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的建议,可以使用关联类型使其更加通用。我稍微修改了示例以展示此方法的好处。

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

我只是想表达一下我的看法。我们需要一个协议,返回应用于该协议的类型的可选项。我们还希望覆盖方法明确地返回类型,而不仅仅是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作为返回类型了。编译器有所改变。我已经有一段时间没有使用这样的解决方法了。 - Mark A. Donohoe

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

除了使用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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接