web-dev-qa-db-ja.com

Selfを返すプロトコル関数

オブジェクトのコピーを返すプロトコル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()
    }
}

その結果、次のエラーが発生しました。

プロトコル「P」に準拠するには、非最終クラス「C」のメソッド「copy()」がSelfを返す必要があります

class Cfinalをプレフィックスする場合を除いて、何も機能しません。

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

ただし、Cをサブクラス化する場合、何も機能しません。これを回避する方法はありますか?

67
aeubanks

問題は、コンパイラがあなたが保持することを証明できないという約束をしていることです。

したがって、このプロミスを作成しました。copy()を呼び出すと、完全に初期化された独自の型が返されます。

ただし、copy()を次のように実装しました。

_func copy() -> Self {
    return C()
}
_

今、私はcopy()をオーバーライドしないサブクラスです。そして、完全に初期化されたC(私が約束した)ではなく、Selfを返します。だからそれはダメです。どうですか:

_func copy() -> Self {
    return Self()
}
_

まあ、それはコンパイルしませんが、それができたとしても、それは良くないでしょう。サブクラスには些細なコンストラクターがない場合があるため、D()は正当ではない場合もあります。 (以下を参照してください。)

OK、まあどうですか:

_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++についてはしばらく考えることができますが、can makeの約束があることを思い出してください。私たちと私たちのすべてのサブクラスが特定の種類の初期化子を実装することを約束することができ、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.
  }
}
_

ここでは、コピーを実行するイニシャライザがあることをお約束します。そして、実行時に呼び出すものを決定し、探していたメソッド構文を提供します。

136
Rob Napier

Swift 2では、これにプロトコル拡張を使用できます。

protocol Copyable {
    init(copy:Self)
}

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

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()
13
Matt Mendrala

実際には、プロトコルで要求されたときに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
10
werediver

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)
    }
}
2
David James

私は同様の問題を抱えていて、役に立つかもしれないものを思いついたので、今後の参考のために共有したいと思いますが、これは解決策を探すときに最初に見つけた場所の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() }       
}

もちろん、私のコンテキストと機能は完全に異なりますが、質問の精神で、可能な限り与えられた例に近づけるように努めました。

1
Alain T.

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
Au Ris

ここでリングに帽子を投げるだけです。プロトコルが適用されたタイプのオプションを返すプロトコルが必要でした。また、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
MarqueIV