8

メソッドのスウィズリングを利用したいのですが、簡単な例でさえうまくいきません。概念が何であるかを誤解している可能性がありますが、私が知る限り、メソッドの実装を入れ替えることができます。

A と B の 2 つのメソッドが与えられた場合、A を呼び出すと代わりに B が実行されるように、それらの実装を入れ替えたいと思います。スウィズリングの例をいくつか見つけました ( example1およびexample2 )。これをテストするクラスを含む新しいプロジェクトを作成しました。

class Swizzle: NSObject
{
    func method()
    {
        print("A");
    }
}

extension Swizzle
{
    override class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0;
        }

        // make sure this isn't a subclass
        if (self !== Swizzle.self)
        {
            return;
        }

        dispatch_once(&Static.token)
        {
            let originalSelector = Selector("method");
            let swizzledSelector = Selector("methodExt");

            let originalMethod = class_getInstanceMethod(self, originalSelector);
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector);

            print(method_getImplementation(originalMethod));
            print(method_getImplementation(swizzledMethod));

            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);
            }

            print(method_getImplementation(originalMethod));
            print(method_getImplementation(swizzledMethod));
        }
    }

    func methodExt()
    {
        print("B");
    }
}

次に、それを実行しようとします

var s = Swizzle();
s.method();

予想される出力は「B」ですが、「A」はまだ印刷中です。私のコードからわかるようIMPに、スウィズル操作の前後にそれぞれのプリントを含めました。これらのプリントは、交換が行われていることを示していますが、出力は同じままです。

出力:

0x000000010251a920
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A

これらの変更を有効にすることに関して、私が見逃しているものはありますか?

PS。現在XCode 7.0.1を使用しています

4

1 に答える 1

16

問題はmethod()dynamicディレクティブがないことです:

class Swizzle: NSObject
{
    dynamic func method()
    {
        print("A")
    }
}

宣言を変更すると、機能するはずです。

Swift でメソッド スウィズリングを使用する場合、クラス/メソッドが準拠する必要がある 2 つの要件があります。

  • あなたのクラスは拡張する必要がありますNSObject
  • スウィズルしたい関数にはdynamic属性が必要です

これが必要な理由の完全な説明については、Using Swift with Cocoa and Objective-C を参照してください。

動的ディスパッチの要求

この@objc属性は Swift API を Objective-C ランタイムに公開しますが、プロパティ、メソッド、添え字、または初期化子の動的ディスパッチを保証しません。Swift コンパイラは、Objective-C ランタイムをバイパスして、コードのパフォーマンスを最適化するためにメンバー アクセスを非仮想化またはインライン化する場合があります。メンバー宣言を修飾子でマークすると、dynamicそのメンバーへのアクセスは常に動的にディスパッチされます。修飾子でマークされた宣言はdynamic、Objective-C ランタイムを使用してディスパッチされるため、@objc属性で暗黙的にマークされます。

動的ディスパッチが必要になることはめったにありません。ただし、dynamicAPI の実装が実行時に置き換えられることがわかっている場合は、修飾子を使用する必要があります。たとえば method_exchangeImplementations、Objective-C ランタイムで関数を使用して、アプリの実行中にメソッドの実装を交換できます。Swift コンパイラがメソッドの実装をインライン化するか、メソッドへのアクセスを非仮想化した場合、新しい実装は使用されません

スウィフト 3 アップデート:

GCD に関していくつかの変更があり、dispatch_once使用できなくなりました。同じ 1 回限りの操作を実行するために、コードをグローバルな静的クラス定数の初期化ブロックで囲むことができます。

Swift 言語は、このコードがアプリケーションの存続期間中に一度だけ実行されることを保証します。

class TestSwizzling : NSObject {
    dynamic func methodOne()->Int{
        return 1
    }
}

extension TestSwizzling {

    //In Objective-C you'd perform the swizzling in load(), 
    //but this method is not permitted in Swift
    override class func initialize()
    {

        struct Inner {
            static let i: () = {

                let originalSelector = #selector(TestSwizzling.methodOne)
                let swizzledSelector = #selector(TestSwizzling.methodTwo)                 
                let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
                let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector)                
                method_exchangeImplementations(originalMethod, swizzledMethod)
            }
        }
        let _ = Inner.i
    }

    func methodTwo()->Int{
        // It will not be a recursive call anymore after the swizzling
        return methodTwo()+1
    }
}

var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())

スウィフト 2.2 アップデート:

新しい#selector属性の元の例を更新しました。

class TestSwizzling : NSObject {
    dynamic func methodOne()->Int{
        return 1
    }
}

extension TestSwizzling {

    //In Objective-C you'd perform the swizzling in load(), 
    //but this method is not permitted in Swift
    override class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0
        }

        // Perform this one time only
        dispatch_once(&Static.token)
        {
                let originalSelector = #selector(TestSwizzling.methodOne)
                let swizzledSelector = #selector(TestSwizzling.methodTwo)                 
                let originalMethod = class_getInstanceMethod(self, originalSelector);
                let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)                
                method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }

    func methodTwo()->Int{
        // It will not be a recursive call anymore after the swizzling
        return methodTwo()+1
    }
}

var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())

試してみる例が必要な場合は、github でこのサンプル プロジェクトを確認してください。

于 2015-10-13T08:23:18.007 に答える