web-dev-qa-db-ja.com

Swiftには動的ディスパッチと仮想メソッドがありますか?

C++/Java/C#のバックグラウンドから来ると、Swiftで仮想メソッドが表示されることを期待していましたが、Swiftのドキュメントを読むと、仮想メソッドについての言及がありません。

何が足りないのですか?

22
Ian Ringrose

C++とは異なり、Swiftでメソッドが仮想であることを指定する必要はありません。コンパイラーは、次のうちどれを使用するかを決定します。

(パフォーマンスメトリックはもちろんハードウェアによって異なります)

  • メソッドをインライン化:0 ns
  • 静的ディスパッチ:<1.1ns
  • 仮想ディスパッチ1.1ns(指定されている場合はJava、C#、C++など)。
  • ダイナミックディスパッチ4.9ns(Objective-Cなど)。

もちろん、Objective-Cは常に後者を使用します。 4.9nsのオーバーヘッドは、メソッドの実行時間全体のごく一部に相当するため、通常は問題になりません。ただし、必要に応じて、開発者はCまたはC++にシームレスにフォールバックできます。ただし、Swiftでは、コンパイラーは最も高速なものを分析し、ユーザーに代わって決定を試みます。インライン、静的、仮想を優先しますが、Objective-Cの相互運用性のためにメッセージングを保持します。メソッドをdynamicでマークして、メッセージングを促進することができます。

これの副作用の1つは、動的ディスパッチによって提供される強力な機能の一部が利用できない可能性があることです。これは、以前はすべてのObjective-Cメソッドに当てはまると想定されていたためです。動的ディスパッチはメソッドインターセプトに使用され、メソッドインターセプトは次のユーザーによって使用されます。

  • ココアスタイルのプロパティオブザーバー。
  • CoreDataモデルオブジェクトのインストルメンテーション。
  • アスペクト指向プログラミング

上記の種類の機能は、late binding言語によって提供される機能です。 Javaはメソッド呼び出しにvtableディスパッチを使用しますが、それでも遅延バインディング言語と見なされているため、仮想マシンとクラスローダーシステムを備えているため、上記の機能を利用できます。ランタイムインストルメンテーションを提供するためのアプローチ。 "Pure" Swift(Objective-C相互運用なし)は、静的ディスパッチを備えた直接実行可能なコンパイル済み言語であるという点でC++に似ており、これらの動的機能ARCの伝統では、これらの種類の機能の多くがコンパイル時間に移行するのを目にするかもしれません。これは、モバイルコンピューティングにおける重要な考慮事項である「ワットあたりのパフォーマンス」に関してエッジを与えます。

18
Jasper Blues

すべてのメソッドは仮想です。ただし、overrideキーワードを使用して、基本クラスのメソッドをオーバーライドすることを宣言する必要があります。

Swiftプログラミングガイド から:

オーバーライド

サブクラスは、インスタンスメソッド、クラスメソッド、インスタンスプロパティ、またはスーパークラスから継承する添え字の独自のカスタム実装を提供できます。これはオーバーライドとして知られています。

継承される特性をオーバーライドするには、オーバーライドする定義の前にoverrideキーワードを付けます。そうすることで、オーバーライドを提供するつもりであり、誤って一致する定義を提供していないことが明確になります。誤ってオーバーライドすると、予期しない動作が発生する可能性があり、overrideキーワードのないオーバーライドは、コードのコンパイル時にエラーと診断されます。

overrideキーワードは、Swiftコンパイラーに、オーバーライドするクラスのスーパークラス(またはその親の1つ)に、オーバーライド用に指定した宣言と一致する宣言があることを確認するように求めます。これチェックは、オーバーライドする定義が正しいことを確認します。

7
trojanfoe
_class A {
    func visit(target: Target) {
        target.method(self);
    }
}

class B: A {}

class C: A {
    override func visit(target: Target) {
        target.method(self);
    }
}

class Target {
    func method(argument: A) {
        println("A");
    }

    func method(argument: B) {
        println("B");
    }

    func method(argument: C) {
        println("C");
    }
}

let t = Target();
let a:  A = A();
let ab: A = B();
let b:  B = B();
let ac: A = C();
let c:  C = C();

a.visit(t);
ab.visit(t);
b.visit(t);
ac.visit(t);
c.visit(t);
_

selfおよびAvisit()C参照に注意してください。 Javaの場合と同様に、コピーされませんが、代わりにselfは、オーバーライドで再び使用されるまで同じタイプを保持します。

結果はA、A、A、C、Cであるため、動的ディスパッチは利用できません。残念ながら。

4
Alex Goldstein

Xcode8.x.xおよび9Betaの時点で、C++の仮想メソッドは次のようにSwift 3および4に変換される可能性があります。

protocol Animal: AnyObject {  // as a base class in C++; class-only protocol in Swift
  func hello()
}

extension Animal {  // implementations of the base class
  func hello() {
    print("Zzz..")
  }
}

class Dog: Animal {  // derived class with a virtual function in C++
  func hello() {
    print("Bark!")
  }
}

class Cat: Animal {  // another derived class with a virtual function in C++
  func hello() {
    print("Meow!")
  }
}

class Snoopy: Animal {  // another derived class with no such a function
  //
}

試してみる。

func test_A() {
  let array = [Dog(), Cat(), Snoopy()] as [Animal]
  array.forEach() {
    $0.hello()
  }
  //  Bark!
  //  Meow!
  //  Zzz..
}

func sayHello<T: Animal>(_ x: T) {
  x.hello()
}

func test_B() {
  sayHello(Dog())
  sayHello(Cat())
  sayHello(Snoopy())
  //  Bark!
  //  Meow!
  //  Zzz..
}

要約すると、C++で行う同様のことは、SwiftのProtocolおよびGenericで実現できると思います。

私もC++の世界から来て、同じ質問に直面しました。上記は機能しているように見えますが、C++の方法のように見えますが、多少迅速な方法ではありません。

それ以上の提案は大歓迎です!

2
Tora