1. ホーム
  2. swift

[解決済み] Swift の拡張機能でメソッドをオーバーライドする

2022-05-06 10:22:09

質問

私は、必要なもの(ストアドプロパティやイニシャライザ)だけをクラス定義に入れ、それ以外はすべて独自の extension のようなものです。 extension でグループ化し、論理ブロックごとに // MARK: も同様です。

例えばUIViewのサブクラスでは、レイアウト関連の拡張機能、イベントの購読と処理などの拡張機能を持つことになります。これらの拡張機能では、必然的にいくつかのUIKitのメソッドをオーバーライドする必要があります。 layoutSubviews . この方法で問題に気づいたことはありませんでした -- 今日まで。

例えば、このクラス階層を考えてみましょう。

public class C: NSObject {
    public func method() { print("C") }
}

public class B: C {
}
extension B {
    override public func method() { print("B") }
}

public class A: B {
}
extension A {
    override public func method() { print("A") }
}

(A() as A).method()
(A() as B).method()
(A() as C).method()

出力は A B C . それは私にはほとんど意味がありません。プロトコル拡張は静的にディスパッチされると書いてありましたが、これはプロトコルではありません。これは普通のクラスで、メソッドの呼び出しは実行時に動的にディスパッチされると思います。明らかに C は、少なくとも動的にディスパッチされて C ?

から継承を外すと NSObject を作成し C をルートクラスとすると、コンパイラは次のような文句を言います。 declarations in extensions cannot override yet というのは、すでに読んだことがあります。しかし、どのようにして NSObject をルートクラスとして使用すると、状況は変化するのでしょうか?

両方のオーバーライドをクラス宣言に移動させると A A A を移動させると、予想通り B が生成されます。 A B B 移動のみ A が生成されます。 C B C を静的に型付けしたものでさえ、私には全く意味がありません。 A を生成します。 A -を出力するようになりました。

を追加することで dynamic キーワードを定義またはオーバーライドすることで、「クラス階層内のその時点から下」の望ましい動作が得られるようです...

この質問を投稿するきっかけとなったのは、この例から少し離れたところにあるのですが、例を変えてみましょう。

public class B: UIView {
}
extension B {
    override public func layoutSubviews() { print("B") }
}

public class A: B {
}
extension A {
    override public func layoutSubviews() { print("A") }
}


(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()

これで A B A . ここで、UIViewのlayoutSubviewsをどう考えてもダイナミックにすることができない。

両方のオーバーライドをクラス宣言に移すと、次のようになります。 A A A また、Aのみ、Bのみでも、やはり A B A . dynamic は、私の問題を解決してくれました。

理論的には dynamic に、すべての override しかし、私はここで何か他の間違ったことをしているような気がするのです。

を使用することは本当に間違っているのでしょうか? extension は、私のようにコードをグループ化するために使用するのですか?

どのように解決するのですか?

拡張機能がオーバーライドできない/してはいけない。

Apple の Swift Guide に記載されているように、エクステンションの機能 (プロパティやメソッドなど) を上書きすることはできません。

拡張機能は、型に新しい機能を追加することはできますが、既存の機能を上書きすることはできません。

Swift開発者ガイド

コンパイラは、Objective-Cとの互換性のために、拡張機能でオーバーライドすることを許可しています。しかし、実際には言語指令に違反しているのです。

アイザック・アシモフの「"」を思い出したよ。 ロボット工学の三原則 ということです。

拡張機能 ( 構文糖 ) は、それ自身の引数を受け取る独立したメソッドを定義します。に対して呼び出される関数、すなわち layoutSubviews は、コードがコンパイルされたときにコンパイラが知っているコンテキストに依存します。UIView は NSObject を継承した UIResponder を継承しています。 そのため、拡張機能でのオーバーライドは許可されていますが、そのようなことはすべきではありません。 .

つまり、グループ化することは悪いことではありませんが、拡張機能ではなくクラスでオーバーライドする必要があります。

ディレクティブの注意点

を使用することができます。 override スーパークラスのメソッド、すなわち load() initialize() を、Objective-C互換のメソッドであれば、サブクラスの拡張子で使用することができます。

そのため、なぜ layoutSubviews .

すべての Swift アプリは、Swift 専用のランタイムを可能にする純粋な Swift 専用のフレームワークを使用する場合を除き、Objective-C ランタイム内で実行されます。

Objective-Cのランタイムは、一般的に2つのクラスのメインメソッドを呼び出すことがわかりました。 load()initialize() は、アプリのプロセスでクラスを初期化する際に自動的に使用されます。

について dynamic モディファイア

から アップルデベロッパーライブラリ (アーカイブ.org)

を使用することができます。 dynamic 修飾子を使用すると、Objective-C ランタイムを通じてメンバーへのアクセスが動的にディスパッチされることを要求できます。

Swift API が Objective-C ランタイムによってインポートされるとき、プロパティ、メソッド、添え字、またはイニシャライザーの動的ディスパッチは保証されません。 Swift コンパイラは、Objective-C ランタイムをバイパスして、コードのパフォーマンスを最適化するために、メンバアクセスを仮想化またはインライン化することがあります。 ????

そこで dynamic を適用することができます。 layoutSubviews -> UIView Class はObjective-Cで表現され、そのメンバーへのアクセスは常にObjective-Cのランタイムを使用して使用されるからです。

そのため、コンパイラが許可している overridedynamic .