togo

= ひとりごと to go

zumuya の人による机の上系情報サイト

Swift から Method Swizzling

  • Apple
  • 開発

昔はよく使ってた Method Swizzling だけど、Objective-C ランタイムの機能なので Swift 時代になってからはなんとなく手を出さずにいた。でも実際に書いてみると何も import せずに関連関数を呼び出せたし、普通にすっきりしたコードで実現できるのね。

extension NSObject
{
    public class func swizzle(_ originalSelector: Selector, to replacedSelector: Selector)
    {
        method_exchangeImplementations(
            class_getInstanceMethod(self, originalSelector)!,
            class_getInstanceMethod(self, replacedSelector)!
        )
    }
}
少し便利な定義
extension NSCursor
{
    @objc public func swizzle_set()
    {
        //call original method
        swizzle_set()

        if (self == .arrow), NSEvent.modifierFlags.contains(.shift) {
            NSLog("\(self) set! \(Thread.callStackSymbols) \(NSApp.currentEvent)")
        }
    }
}

NSCursor.swizzle(#selector(NSCursor.set), to: #selector(NSCursor.swizzle_set))
使用例

例えばカーソルの変更みたいに頻繁に呼ばれる処理を調べたい場合、Xcode のデバッグ機能でシンボル -[NSCursor set] をトリガーに指定して止めるやり方では必要なタイミングに限定しにくいけど、Method Swizzling を使用すると「対象が黒矢印カーソルのとき」「Shift キーが押されているとき」のように好きな条件で発動させることができて簡単だ。

多用していたあの頃は SIMBL プラグインを作るという目的があったから特定の接頭辞のついた大量のメソッドを一括で置き換えるような処理を書いたりもしていたけど、単純にアプリケーションのバグ原因を調査する目的1であればそこまで必要ないでしょう。


  1. 実際は悩んだ末にここまでやってたどり着くのが AppKit 側のバグだったりするのが泣ける。 ↩︎

Share

リンクも共有もお気軽に。記事を書くモチベーションの向上に役立てます。