自己を返すプロトコル関数


82

オブジェクトのコピーを返すプロトコル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 Cfinalすなわち操作を行います。

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

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


1
「何も機能しない」とはどういう意味ですか?
ロブ・ネーピア

コンパイラは、CまたはSelfのいずれかを戻り値として入力するclassと、afinal class
aeubanks 2014

6
OK、エラーを再現しましたが、質問するときは、返される実際のエラーを含める必要があります。「エラーが発生する」または「機能しない」だけではありません。
ロブ・ネーピア

ところで、コンパイラはここでのエラーが完全に正しいです。自分がやろうとしていることを全部手に入れることができるかどうかを考えているだけです。
ロブ・ネーピア

1
しかし、あなたは呼び出すことができます[[[self class] alloc] init]。したがって、問題は、現在のクラスを呼び出してinitメソッドを呼び出すタイプセーフな方法があるかどうかということだと思います。
aeubanks 2014

回答:


144

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

したがって、この約束を作成しました。呼び出し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 ++を考えると、我々は約束があることを思い出すことができることができますしますが。私たちとすべてのサブクラスが特定の種類の初期化子を実装することを約束でき、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 2014

2
最後のpure-Swiftソリューションは、init(copy: C)代わりに実装する必要があるため、サブクラスでは機能しませんinit(copy: Self):(
Fluidsonic 2014年

最後の解決策は戻り値がであることを保証しますSelfが、初期化子はすべて静的に型指定された変数を受け入れる必要があります。つまり、最初にC返すだけではそれほど改善されませんAnyObject
シャクリット2015年

1
SWIFT 2.0では、あなたは、init明示的に呼び出す必要があるだろう:self.dynamicType.init( ... )
pronebird

1
@DscheeはC内にあり、SelfはCまたはCのサブクラスである可能性があります。これらは異なるタイプです。
ロブ・ネーピア

25

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 2016年

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


これは私が興味を持っていることをします。ありがとう!
TheNerderyでのJosh 2018

10

実際には、プロトコルで必要なときに簡単に戻ることができるトリックが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

1
ワオ。コンパイルします。コンパイラがあなたをただ許可しないので、それはトリッキーですreturn Vehicle() as! Self
SimplGy 2016

それは気が遠くなるようなものです。ワオ。私がここで尋ねているのは、実際にはこれのバリエーションですか?stackoverflow.com/q/42041150/294884
Fattie 2017

@JoeBlowそうではないのではないかと思います。私は、我々は(戻り値の型を正確に知っている、すなわちない「AまたはB」必要があり、安全な私たちの心を保つために言うだろうが、ちょうど「A」は、そうでない場合、我々はポリモーフィズム+継承+機能のオーバーロード(少なくとも)を考える必要があります。
werediver

それはコンパイラのトリックです。のオーバーライドfoo()は強制されないため、カスタム実装のVehicleないすべての子孫foo()はで明らかなクラッシュを生成しautocast()ます。例:class SuperCar: Vehicle { } let superCar = SuperCar.foo() 。のインスタンスをVehicleダウンキャストすることはできません。SuperCarそのため、「autocast()」でnilを強制的にアンラップすると、クラッシュします。
freennnn 2017年

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 2017年

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

私も同様の問題を抱えていて、役に立つかもしれない何かを思いついたので、これは解決策を探すときに最初に見つけた場所の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

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

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()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.