便宜上、フレームワークのバグを回避するために、そして絶望のために、誰かが作成したクラスメソッドの動作を再定義する必要がある場合があります。 メソッドスウィズリングを使用すると、元の実装を利用可能なまま、メソッドをランタイムで直接メソッドに置き換えることができます。
Objective-Cランタイムの記事
。 このプロセス
の理論と実際の応用はよく説明されています。 しかし、Swiftへの移行に伴い、いくつかのニュアンスがあります。
例から始めましょう。 サードパーティの
Chartsフレームワークに
ChartRendererクラスがあり、その中に
drawDotsメソッドがあり、チャート上の節点に円を
描くとします。 そして、円ではなく、アスタリスクが必要です。 そして、フレームワークの内部に登りたくはありません。更新が時々出て、それらを失うのは残念だからです。
そのため、
ChartRendererクラスの拡張機能を作成します。これにより、ランタイムで
drawDotsメソッドが
drawStarsメソッドに直接置き換えられます。
drawStarsメソッド
がdrawDotsメソッドの呼び出しに応答する
たびに 、
Chartsの他のオブジェクトが
ChartRendererにアクセスする方法を心配する必要はありません。
extension ChartRenderer { public override class func initialize() { struct Static { static var token: dispatch_once_t = 0 } // , if self !== UIViewController.self { return } dispatch_once(&Static.token) { let originalSelector = Selector("drawDots:") let swizzledSelector = Selector("drawStars:") let originalMethod = class_getInstanceMethod(self, originalSelector) let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) if didAddMethod { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) } else { method_exchangeImplementations(originalMethod, swizzledMethod) } } } // func drawStars(color: CGColor) { // // , drawStars(...), // drawDots(...), "drawStars:" } }
Objective-Cのすべての資料では、メソッドは
初期化()ではなく
load()で置き換える必要があると書いています。これは、最初のメソッドはクラスに対して1回だけ呼び出され、2番目はすべてのサブクラスに対して呼び出されるためです ただし、Swiftを使用する場合のランタイムは
load()をまったく呼び出さないため、この
initialize()が子孫ではなくクラスによって呼び出され、
dispatch_onceで 1回だけ置換が行われるようにする必要
があります。
シンプルではあるが視覚的ではない代替オプションがあることに注意してください:拡張の代わりに、
アプリケーションの AppDelegateでこれらのすべての操作を
実行し
ます(_:didFinishLaunchingWithOptions :) 。
ChartRendererが Objective-Cで記述されている場合、すべてがすでにそのように動作するはずです。 良い記事はNSHipsterにあります。
次に、ニュアンスについて説明します。 プロジェクトと、置き換え可能なメソッドを含むクラスの両方がSwiftで記述されている場合にメソッドスウィズリングを使用する場合、これは2つの要件を満たす必要があります。
- 彼はNSObjectの相続人でなければなりません
- 置き換えられたメソッドには動的属性が必要です
このようなもの:
class ChartRenderer: NSObject { dynamic func drawDots() {
このニュアンスの性質については、記事「
CocoaおよびObjective-CでSwiftを使用する」で説明されています。
動的ディスパッチが必要
objc属性はSwift APIをObjective-Cランタイムに公開しますが、プロパティ、メソッド、サブスクリプト、または初期化子の動的なディスパッチを保証するものではありません。 Swiftコンパイラは、Objective-Cランタイムをバイパスして、コードのパフォーマンスを最適化するために、メンバーアクセスを仮想化またはインライン化する場合があります。 dynamic修飾子でメンバー宣言をマークすると、そのメンバーへのアクセスは常に動的にディスパッチされます。 dynamic修飾子でマークされた宣言は、Objective-Cランタイムを使用してディスパッチされるため、暗黙的にobjc属性でマークされます。
動的なディスパッチを要求する必要はほとんどありません。 ただし、APIの実装が実行時に置き換えられることがわかっている場合は、動的修飾子を使用する必要があります。 たとえば、Objective-Cランタイムでmethod_exchangeImplementations関数を使用して、アプリの実行中にメソッドの実装を交換できます。 Swiftコンパイラーがメソッドの実装をインライン化するか、メソッドへの仮想化アクセスを行った場合、新しい実装は使用されません。
上記の記事に加えて、英語で問題が特定のBartek Hugoによって
ここで説明されてい
ます 。