swift 返回自身的协议函数

qvsjd97n  于 2023-09-30  发布在  Swift
关注(0)|答案(9)|浏览(158)

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

protocol P {
    func copy() -> Self
}

C类实现了P:

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

但是,无论我是否将返回值设置为Self,我都会得到以下错误:
无法将类型“C”的返回表达式转换为返回类型“Self”
我也试过返回C

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

这导致了以下错误:
非final类“C”中的方法“copy()”必须返回Self以符合协议“P”
除了我在class C前面加上final的情况外,什么都不起作用:

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

但是,如果我想子类化C,那么什么都不起作用。有什么办法可以解决这个问题吗?

q3qa4bjr

q3qa4bjr1#

问题是你做出了一个编译器无法证明你会遵守的承诺。
所以你创造了这个承诺:调用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.
  }
}

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

xeufq47z

xeufq47z2#

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

protocol Copyable {
    init(copy:Self)
}

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

bxfogqkk3#

还有另一种方法可以做你想做的事情,这涉及到利用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()
ecfsfe2w

ecfsfe2w4#

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

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

twh00eeo5#

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

protocol P { 
     func id() -> Self 
 }

这是:

class D : P { 
     func id() -> Self { 
         return D()
     } 
 }

获取

error: repl.swift:11:16: error: cannot convert return expression of type 'D' to return type 'Self'
        return D()
               ^~~
                   as! Self

而这:

class D : P { 
     func id() -> Self { 
         return D() as! Self
     } 
 }

工程.

2exbekwf

2exbekwf6#

根据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)
    }
}
bkhjykvo

bkhjykvo7#

我有一个类似的问题,并提出了一些可能是有用的,所以我想我会分享它为未来的参考,因为这是我发现的第一个地方时,寻找一个解决方案。
如上所述,问题在于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() }       
}

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

5anewei6

5anewei68#

我只是想试试。我们需要一个协议,它返回一个可选的类型的协议被应用。我们还希望重写显式地返回类型,而不仅仅是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? {
        ...
    }
}
lokaqttq

lokaqttq9#

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

相关问题