Swift拡張機能でのメソッドのオーバーライド


133

私は必要なもの(保存されたプロパティ、初期化子)だけをクラス定義に入れ、他のものすべてを独自のに移動する傾向があります。これは、グループ化する論理ブロックごと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、私には全く意味がありません最後のうち、:静的に型付けされていなくても1がA生成A、それ以上-outputを!

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私のようにコードをグループ化するためにs を使用するのは本当に間違っていますか?


この方法で拡張機能を使用することは、Swiftの規則です。Appleでさえ標準ライブラリでそれを行います。
アレクサンダー-モニカを復活させる2016


1
@AMomchilovあなたがリンクした文書はプロトコルについて話します、何かが足りませんか?
クリスチャンシュノール2016

私は、それはその両方のための作品と同じ仕組みだ疑い
アレクサンダー-復活モニカ

3
サブクラス拡張でオーバーライドされたメソッドへのSwiftディスパッチを複製するように見えます。マットの答えはそれがバグだということです(そして彼はそれをサポートするためにドキュメントを引用しています)。
jscs 16

回答:


229

拡張機能はオーバーライドできません。

AppleのSwiftガイドに記載されているように、拡張機能の機能(プロパティやメソッドなど)をオーバーライドすることはできません。

拡張機能はタイプに新しい機能を追加できますが、既存の機能をオーバーライドすることはできません。

Swift開発者ガイド

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

hatアイザックアシモフの「ロボット工学の3つの法則」を思い出しました 🤖

拡張(構文シュガー)は、独自の引数を受け取る独立したメソッドを定義します。つまりlayoutSubviews、コードがコンパイルされるときにコンパイラーが認識しているコンテキストによって、呼び出される関数が異なります。UIViewはNSObjectから継承するUIResponderから継承するため、拡張機能でのオーバーライドは許可されていますが、

したがって、グループ化には何の問題もありませんが、拡張機能ではなくクラスでオーバーライドする必要があります。

ディレクティブノート

メソッドがObjective-Cと互換性がある場合はoverride、スーパークラスのメソッド、つまりload() initialize()サブクラスの拡張でのみ実行できます。

したがって、これを使用してコンパイルできる理由を確認できますlayoutSubviews

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

Objective-Cランタイムは通常、2つのクラスのメインメソッドload()を呼び出しinitialize()、アプリのプロセスでクラスを初期化するときに自動的に呼び出します。

dynamic修飾子について

Appleデベロッパライブラリ (archive.org)

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

Swift APIがObjective-Cランタイムによってインポートされる場合、プロパティ、メソッド、添え字、または初期化子の動的ディスパッチは保証されません。Swiftコンパイラーは、Objective-Cランタイムをバイパスして、コードのパフォーマンスを最適化するために、メンバーアクセスを仮想化またはインライン化する可能性があります。😳

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

そのため、コンパイラでoverrideand を使用できますdynamic


6
拡張機能は、クラスで定義されたメソッドのみをオーバーライドできません。親クラスで定義されたメソッドをオーバーライドできます。
RJE 2016

-Swift3-まあ、それは奇妙です。なぜなら、あなたが含むフレームワークからメソッドをオーバーライドすることもできます(ここでのオーバーライドとは、スウィズリングのようなものを意味します)。これらのフレームワークは、純粋迅速に書かれていても....多分枠組みものにObjCにしていることを制限されている理由🤔
farzadshbfn

@tymacわかりません。Objective-CランタイムがObjective-Cの互換性のために何かを必要とする場合、Swiftコンパイラーが拡張機能でオーバーライドを許可するのはなぜですか?Swift拡張機能でオーバーライドを構文エラーとしてマークすると、Objective-Cランタイムに害が及ぶ可能性はありますか?
Alexander Vasenin 2017年

1
イライラするので、基本的には、すでにプロジェクト内にあるコードを使用してフレームワークを作成する場合は、サブクラス化してすべての名前を変更する必要があります...
thibaut noah

3
@AuRis参照がありますか?
ricardopereira

18

Swiftの目標の1つは、静的ディスパッチ、または動的ディスパッチの削減です。ただし、Obj-Cは非常に動的な言語です。あなたが見ている状況は、2つの言語間のリンクとそれらが連携する方法から生じています。それは本当にコンパイルすべきではありません。

拡張機能の主なポイントの1つは、拡張用であり、置換/上書きではないということです。名前とドキュメントの両方から、これが意図していることは明らかです。実際、コードからObj-Cへのリンクを削除するNSObjectと(スーパークラスとして削除)、コンパイルされません。

したがって、コンパイラーは静的にディスパッチできるものと動的にディスパッチする必要があるものを決定しようとしていますが、コード内のObj-Cリンクが原因でギャップに陥っています。dynamic「機能する」理由は、Obj-Cリンクをすべてに強制するため、常に動的です。

したがって、グループ化に拡張機能を使用することは間違いありません。それは素晴らしいことですが、拡張機能でオーバーライドすることは間違っています。オーバーライドはメインクラス自体にあり、拡張ポイントを呼び出す必要があります。


これは変数にも当てはまりますか?たとえばsupportedInterfaceOrientationsUINavigationController(異なる方向で異なるビューを表示する目的で)オーバーライドする場合は、拡張ではなくカスタムクラスを使用する必要がありますか?多くの回答では、拡張機能を使用してオーバーライドすることをお勧めしていますsupportedInterfaceOrientationsが、説明が必要です。ありがとう!
Crashalot 2017年

10

サブクラスでオーバーライドを行う機能を維持しながら、クラスの署名と実装(拡張)を明確に分離する方法があります。トリックは、関数の代わりに変数を使用することです

各サブクラスを個別の迅速なソースファイルで定義することを確認した場合、対応する実装を拡張機能できれいに整理しながら、計算された変数をオーバーライドに使用できます。これにより、Swiftの「ルール」が回避され、クラスのAPI /署名が1か所に整理されます。

// ---------- BaseClass.swift -------------

public class BaseClass
{
    public var method1:(Int) -> String { return doMethod1 }

    public init() {}
}

// the extension could also be in a separate file  
extension BaseClass
{    
    private func doMethod1(param:Int) -> String { return "BaseClass \(param)" }
}

...

// ---------- ClassA.swift ----------

public class A:BaseClass
{
   override public var method1:(Int) -> String { return doMethod1 }
}

// this extension can be in a separate file but not in the same
// file as the BaseClass extension that defines its doMethod1 implementation
extension A
{
   private func doMethod1(param:Int) -> String 
   { 
      return "A \(param) added to \(super.method1(param))" 
   }
}

...

// ---------- ClassB.swift ----------
public class B:A
{
   override public var method1:(Int) -> String { return doMethod1 }
}

extension B
{
   private func doMethod1(param:Int) -> String 
   { 
      return "B \(param) added to \(super.method1(param))" 
   }
}

各クラスの拡張機能はプライベートであり、お互いに表示されないため(別々のファイルにある限り)、同じメソッド名を実装に使用できます。

ご覧のとおり、継承(変数名を使用)はsuper.variablenameを使用して適切に機能します

BaseClass().method1(123)         --> "BaseClass 123"
A().method1(123)                 --> "A 123 added to BaseClass 123"
B().method1(123)                 --> "B 123 added to A 123 added to BaseClass 123"
(B() as A).method1(123)          --> "B 123 added to A 123 added to BaseClass 123"
(B() as BaseClass).method1(123)  --> "B 123 added to A 123 added to BaseClass 123"

2
それは私自身のメソッドでは機能すると思いますが、私のクラスでシステムフレームワークメソッドをオーバーライドする場合は機能しません。
Christian Schnorr

これにより、プロパティラッパーの条件付きプロトコル拡張の正しい道が見えてきました。ありがとう!
Chris Prince、

1

この答えは、OPを対象としたものではなく、彼の発言によって反応するように刺激を受けたという事実を除いて、「必要なもの(格納されたプロパティ、初期化子)だけをクラス定義に入れ、その他すべてを独自の拡張に移動する傾向があります。 ……」私は主にC#プログラマーです。C#では、この目的で部分クラスを使用できます。たとえば、Visual Studioは、部分クラスを使用して、UI関連のものを別のソースファイルに配置し、メインのソースファイルを整理しておくので、邪魔になりません。

「swift部分クラス」を検索すると、拡張機能を使用できるためSwiftは部分クラスを必要としないとSwiftの支持者が言っているさまざまなリンクが見つかります。興味深いことに、Googleの検索フィールドに「swift拡張機能」と入力すると、最初の検索候補は「swift拡張機能のオーバーライド」であり、現時点ではこのスタックオーバーフローの質問が最初のヒットです。私は、オーバーライド機能の(欠如)に関する問題がSwift拡張機能に関連する最も検索されているトピックであることを意味し、少なくとも派生クラスを使用する場合、Swift拡張機能が部分クラスを置換できない可能性があるという事実を強調しますプログラミング。

とにかく、長々とした紹介を短くするために、ボイラープレート/バゲッジメソッドを、C#-to-Swiftプログラムが生成していたSwiftクラスのメインソースファイルから移動したい状況でこの問題に遭遇しました。これらのメソッドを拡張機能に移動した後、オーバーライドが許可されないという問題に遭遇した後、私は次のような単純な回避策を実装することになりました。メインのSwiftソースファイルには、拡張ファイルの実際のメソッドを呼び出す小さなスタブメソッドがまだいくつか含まれており、これらの拡張メソッドには、オーバーライドの問題を回避するために一意の名前が付けられています。

public protocol PCopierSerializable {

   static func getFieldTable(mCopier : MCopier) -> FieldTable
   static func createObject(initTable : [Int : Any?]) -> Any
   func doSerialization(mCopier : MCopier)
}

public class SimpleClass : PCopierSerializable {

   public var aMember : Int32

   public init(
               aMember : Int32
              ) {
      self.aMember = aMember
   }

   public class func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_SimpleClass(mCopier: mCopier)
   }

   public class func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_SimpleClass(initTable: initTable)
   }

   public func doSerialization(mCopier : MCopier) {
      doSerialization_SimpleClass(mCopier: mCopier)
   }
}

extension SimpleClass {

   class func getFieldTable_SimpleClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_SimpleClass(initTable : [Int : Any?]) -> Any {
      return SimpleClass(
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_SimpleClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367620, 1)
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

public class DerivedClass : SimpleClass {

   public var aNewMember : Int32

   public init(
               aNewMember : Int32,
               aMember : Int32
              ) {
      self.aNewMember = aNewMember
      super.init(
                 aMember: aMember
                )
   }

   public class override func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_DerivedClass(mCopier: mCopier)
   }

   public class override func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_DerivedClass(initTable: initTable)
   }

   public override func doSerialization(mCopier : MCopier) {
      doSerialization_DerivedClass(mCopier: mCopier)
   }
}

extension DerivedClass {

   class func getFieldTable_DerivedClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376443905] = { () in try mCopier.getInt32A() }  // aNewMember
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_DerivedClass(initTable : [Int : Any?]) -> Any {
      return DerivedClass(
                aNewMember: initTable[376443905] as! Int32,
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_DerivedClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367621, 2)
      mCopier.serializeProperty(376443905, .eInt32, { () in mCopier.putInt32(aNewMember) } )
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

冒頭で述べたように、これは実際にはOPの質問に答えるものではありませんが、この単純な回避策が、メソッドをメインソースファイルから拡張ファイルに移動してnoに実行したい他の人に役立つことを願っています-問題を上書きします。


1

POP(プロトコル指向プログラミング)を使用して、拡張機能の関数をオーバーライドします。

protocol AProtocol {
    func aFunction()
}

extension AProtocol {
    func aFunction() {
        print("empty")
    }
}

class AClass: AProtocol {

}

extension AClass {
    func aFunction() {
        print("not empty")
    }
}

let cls = AClass()
cls.aFunction()

1
これは、プログラマーがAClassの元の定義を制御して、AProtocolに依存できることを前提としています。AClassの機能をオーバーライドしたい状況では、これは通常当てはまりません(つまり、AClassはおそらくAppleが提供する標準ライブラリクラスです)。
ジョナサンレナード

クラスの元の定義を変更したくない、または変更できない場合は、(場合によっては)拡張またはサブクラスでプロトコルを適用できます。
シム、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.