web-dev-qa-db-ja.com

Swift関数スウィズリング/ランタイム

Swift以前は、Objective-Cでは、<objc/runtime.h>を使用してクラスのメソッドをスウィズルまたはフックしていました。

Swiftのランタイムの変更と、CydiaSubstrateやこの分野で役立つ他のライブラリなどのフック関数のトピックに関する情報を誰かが持っている場合は、お知らせください。

20
atomikpanda

Swiftでのメソッドのスウィズリングで成功しました。この例は、NSDictionaryで記述メソッドをフックする方法を示しています

私の実装:

extension NSDictionary {
     func myDescription() -> String!{
        println("Description hooked")
        return "Hooooked " + myDescription();
    }
}

スウィズリングコード:

func swizzleEmAll() {
        var dict:NSDictionary = ["SuperSecret": kSecValueRef]
        var method: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("description"))

        println(dict.description) // Check original description

        var swizzledMethod: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("myDescription"))
        method_exchangeImplementations(method, swizzledMethod)

        println(dict.description) //Check that swizzling works
    }

編集済み:このコードは、任意のカスタムSwiftから継承するクラス)で機能しますNSObject(ただし、機能しないクラスでは機能しません。)その他の例- https://github.com/mbazaliy/MBSwizzler

36
mbazaliy

Objective-Cクラスから継承するSwift生成クラスは、動的メソッドディスパッチを常に使用しているように見えるため、問題なくスウィズルできる可能性があります。ブリッジを介して渡されるため、Objective-Cランタイムに存在するSwiftで定義されたクラスのメソッドをスウィズルできる場合がありますが、Objective-Cのサイドメソッドは、ブリッジを介してSwift-サイドランタイムなので、それらをスウィズルすることが特に役立つかどうかは明確ではありません。

"Pure" Swiftメソッド呼び出しはobjc_msgSendなどを介して動的にディスパッチされているようには見えず、(短い実験から)Swiftはコンパイル時に実装され、実際の型情報の多くは非クラス型の実行時に存在しません(つまり、失われます)(どちらもSwiftの速度の利点とされていると考えられます)。

これらの理由から、Swiftのみのメソッドを有意にスウィズルすることは、Objective-Cメソッドをスウィズリングするよりもかなり難しく、おそらくObjective-Cメソッドのスウィズリングよりもmach_overrideのように見えると思います。

19
ipmcc

他のどの回答も、あらゆる種類のクラスのメソッドのスウィズリングの明確な要件のセットを提供していないため、この質問に1年以上回答しています。

他の人が説明していることは、(NSDictionaryのような)ファウンデーション/ uikitクラスの拡張に対しては問題なく機能しますが、あなた自身の場合にはneverは機能しませんSwiftクラス。

here で説明したように、カスタムクラスでNSObjectを拡張する以外に、メソッドのスウィズリングには追加の要件があります。

スウィズルするSwiftメソッドはマークする必要がありますdynamic

マークを付けないと、メソッドポインターが正しくスワップされているように見えても、ランタイムはスウィズルされたメソッドではなく、元のメソッドを呼び出し続けます。

更新:

私はこの答えを拡張しました ブログ投稿で

5

Swift 2、Cocoapodsを使用して記述されたXcode 7 iOSプロジェクトがありました。Objective-Cソースを使用する特定のCocoapodでは、ポッドをフォークせずに短いメソッドをオーバーライドしたいと思いました。書き込みSwift私の場合、拡張機能は機能しません。

メソッドスウィズリングを使用するために、ココアポッドに置き換え/注入したいメソッドを使用して、メインバンドルに新しいObjective-Cクラスを作成しました。 (ブリッジヘッダーも追加)

Mbazaliyの stackflowのソリューション を使用して、次のようなコードをAppdelegateのdidFinishLaunchingWithOptionsに入れます。

    let mySelector: Selector = "nameOfMethodToReplace"
    let method: Method = class_getInstanceMethod(SomeClassInAPod.self, mySelector)
    let swizzledMethod: Method = class_getInstanceMethod(SomeOtherClass.self, mySelector)
    method_exchangeImplementations(method, swizzledMethod)

これは完全に機能しました。 @mbazaliyのコードの違いは、最初にSomeClassInAPodクラスのインスタンスを作成する必要がなかったことです。私の場合は不可能でした。

注:コードを実行するたびに、コードはメソッドを元のコードと交換するため、Appdelegateにコードを配置しました。コードは1回だけ実行する必要があります。

また、ポッドのバンドルで参照されているいくつかのアセットをメインバンドルにコピーする必要がありました。

2
Nate Flink

mbazaliyが提供するすばらしい答えを拡張したいと思います。

Swiftでスウィズリングを行う別の方法は、Objective-Cブロックを使用して実装を提供することです。

例えばクラスdescriptionNSStringmethodを置き換えるには、次のように記述します。

let originalMethod = class_getInstanceMethod(NSString.self, "description")

let impBlock : @objc_block () -> NSString =
        { () in return "Bit of a hack job!" }

let newMethodImp = imp_implementationWithBlock(unsafeBitCast(impBlock, AnyObject.self))

method_setImplementation(originalMethod, newMethodImp)

これはSwift 1.1以降で機能します。

0
Matteo Pacini

私はそのようにはしませんが、クロージャーが答えを提供すると思います(関数の呼び出しをインターセプト、評価、および転送する機会を与えるため、さらに、リフレクションがある場合とそうである場合に、拡張が容易になります。

http://www.Swift-studies.com/blog/2014/7/13/method-swizzling-in-Swift

0
rougeExciter