6

スウィフトプロトコルは、関数と計算されたプロパティに対して、拡張機能を追加することでデフォルトの実装を提供できます。私はそれを何度もやってきました。 デフォルトの実装は、「代替」としてのみ使用されます。:タイプがプロトコルに準拠しているが、独自の実装を提供していない場合に実行されます。プロトコルで定義されたデフォルトパラメータを持つ関数の実装

少なくとも、それは私がThe Swift Programming Languageガイドを読む方法は次のとおりです。

準拠のタイプは、必要なメソッドやプロパティの独自の実装を提供している場合、その実装は代わりに拡張子が提供するものを使用します。

今、私は特定のプロトコルを実装して、私のカスタムタイプがを行い、状況に遭遇したが、特定の機能の実装を提供するが、それが実行されていない - プロトコル拡張で定義された実装が代わりに実行されます。例えばとして


、私は機能move(to:)、この機能のためのデフォルトの実装を提供拡張有するプロトコルMovable定義:

protocol Movable { 

    func move(to point: CGPoint) 

} 

extension Movable { 

    func move(to point: CGPoint = CGPoint(x: 0, y: 0)) { 
     print("Moving to origin: \(point)") 
    } 

} 

次に、私はCarクラスを定義しますMovableに準拠しており、move(to:)関数の独自の実装を提供しています。

class Car: Movable { 

    func move(to point: CGPoint = CGPoint(x: 0, y: 0)) { 
     print("Moving to point: \(point)") 
    } 

} 

  1. :私は2つの別々の行動を観察し、私はオプションのパラメータpointの値を渡すかどうかに応じて

    let castedCar = Car() as Movable 
    

    は今、私は新しいCar作成しMovableとしてそれをダウンキャスト

    オプションのパラメータをポイントに渡す場合

    Carの実装はと呼ばれる:

    castedCar.move(to: CGPoint(x: 20, y: 10)) 
    

    出力:(20.0、10:

    がポイントに移動します。0)


私はオプションのパラメータに値を与えることなく、 move()機能 を呼び出すと
  • Carの実装は無視され、

    Movableプロトコルのデフォルト実装が代わりに

    と呼ばれます。
    castedCar.move() 
    

    出力:

    が原点に移動:(0.0、0.0)


  • なぜ?

    +3

    私は、コンパイル時にデフォルト値が追加されると考えていますので、変数の静的型を使用する必要があります。 – Sulthan

    +1

    CarをMovableにキャストしているので、Movableメソッドが呼び出されます。あなたがCarをMovableにキャストしないと、あなたのCarの移動方法が呼び出されます –

    +0

    @LeoDabus:あなたが 'Car'を' Movable'にキャストしなければ、私はこの問題に遭遇しません。しかし、私がキャストを実行するときに問題が発生する理由は、私の質問の全体的なポイントです。実際のプロトコル指向の実装では、私が扱っているオブジェクトの実際のクラスを知らないでしょう - 私はそれがプロトコル 'Movable'に準拠していることだけを知っています。 'let castedCar = Car()as Movable'という行は、サンプルコードをきれいに保つために、この状況を模倣する単なる手段です。 – Mischa

    答えて

    8

    これが原因コール

    castedCar.move(to: CGPoint(x: 20, y: 10)) 
    

    プロトコル要件func move(to point: CGPoint)に解決することが可能であるという事実のために、単純である - したがって、コールが動的プロトコル証人テーブルを介しにディスパッチされる(方法によってプロトコルタイプのインスタンスは多態性を達成します)、Carの実装を呼び出すことができます。

    しかし、コール

    castedCar.move() 
    

    は、プロトコル要件func move(to point: CGPoint)と一致していません。したがって、プロトコルwitnessテーブル(プロトコル要件のメソッドエントリのみを含む)を介してディスパッチされません。代わりに、castedCarMovableと入力されているため、コンパイラは静的ディスパッチを使用する必要があります。したがって、プロトコル拡張の実装が呼び出されます。

    デフォルトのパラメータ値は関数の静的な機能に過ぎず、関数の単一のオーバーロードが実際にコンパイラによって出力されます(1つはすべてのパラメータです)。

    デフォルト値を持つパラメータの1つを除外して関数を適用しようとすると、そのデフォルトパラメータ値(定数ではない可能性があるため)の評価を挿入し、その値をコール。

    このため、デフォルトのパラメータ値を持つ関数は、動的ディスパッチではうまく機能しません。デフォルトのパラメータ値を持つメソッドをオーバーライドするクラスでは、予期しない結果を得ることもできます。たとえば、this bug reportを参照してください。デフォルトのパラメータ値のため、必要な動的ディスパッチを取得する


    一つの方法は、単に単にそれでmove(to:)を適用するプロトコル拡張でmove()過負荷と一緒に、あなたのプロトコルでstaticプロパティの要件を定義することです。

    protocol Moveable { 
        static var defaultMoveToPoint: CGPoint { get } 
        func move(to point: CGPoint) 
    } 
    
    extension Moveable { 
    
        static var defaultMoveToPoint: CGPoint { 
         return .zero 
        } 
    
        // simply apply move(to:) with our given defined default. 
        // as defaultMoveToPoint is a protocol requirement, 
        // it can be dynamically dispatched to. 
        func move() { 
         move(to: type(of: self).defaultMoveToPoint) 
        } 
    
        func move(to point: CGPoint) { 
         print("Moving to origin: \(point)") 
        } 
    } 
    
    class Car: Moveable { 
    
        static let defaultMoveToPoint = CGPoint(x: 1, y: 2) 
    
        func move(to point: CGPoint) { 
         print("Moving to point: \(point)") 
        } 
    
    } 
    
    let castedCar: Moveable = Car() 
    castedCar.move(to: CGPoint(x: 20, y: 10)) // Moving to point: (20.0, 10.0) 
    castedCar.move() // Moving to point: (1.0, 2.0) 
    

    defaultMoveToPointは現在、プロトコルの要件であるので - それは動的ので、あなたの希望の動作を与え、に派遣することができます。

    補足として、ではなく、type(of: self)defaultMoveToPointを呼び出しています。これにより、メソッドの呼び出し先の静的メタタイプ値ではなく、動的インスタンスのメタタイプ値が返され、defaultMoveToPointが正しくディスパッチされます。ただし、move()が呼び出される静的型(Moveableを除いて)であれば、Selfを使用できます。

    プロトコル拡張で使用できる動的メタタイプ値と静的メタタイプ値の相違点は、in this Q&Aです。

    +0

    この非常に精巧な答えをありがとう!私はそれからかなり多くを学んだ。 Swiftプログラミング言語ガイドでは、この動作に関する副次的な注意が必要です(実際には静的で動的なディスパッチというコンパイラの詳細が分からない限り)。 – Mischa

    +0

    したがって、一般的に**デフォルトの機能パラメータはプロトコルでは機能しません**。これらは常に静的に解決されるため、ビルド時にコンパイラに「可視」なので、常に呼び出されているのはプロトコル_extension_の実装です。 – Mischa

    +2

    @Mischa助けて嬉しいです:)一般的に言って、デフォルトの機能パラメータ値はプロトコルでは機能しません。パラメータの1つを除外して適用しようとすると、プロトコル要件と一致しなくなり、動的ディスパッチが失われます。与えられたデフォルトのパラメータ値を持つ関数で満たさなければならないことをプロトコル要求のための方法があったとしても、その値の評価*の実装は静的にコンパイラによって決定されます。 – Hamish

    関連する問題