サーチ…


備考

SwiftでメソッドSwizzlingを使用する場合は、クラス/メソッドが準拠しなければならない要件が2つあります。

  • あなたのクラスはNSObject拡張する必要があります
  • swizzleしたい機能は、 dynamic属性を持っていなければなりません

なぜこれが必要なのかについては、 Swift with CocoaとObjective-Cを参照してください:

動的ディスパッチが必要

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

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

リンク

Objective-Cランタイムリファレンス

NSHipsterのメソッドスウィズル

UIViewControllerの拡張とviewDidLoadのスウィズリング

Objective-Cでは、メソッドスウィズリングは、既存のセレクタの実装を変更するプロセスです。これは、セレクタがディスパッチテーブルにマッピングされる方法、または関数やメソッドへのポインタのテーブルのために可能です。

Pure SwiftメソッドはObjective-Cランタイムによって動的にディスパッチされませんが、 NSObjectから継承するすべてのクラスでこれらのテクニックを引き続き利用できます。

ここでは、 UIViewControllerを拡張し、 viewDidLoadをスウィズルしてカスタムロギングを追加します。

extension UIViewController {
    
    // We cannot override load like we could in Objective-C, so override initialize instead
    public override static func initialize() {
        
        // Make a static struct for our dispatch token so only one exists in memory
        struct Static {
            static var token: dispatch_once_t = 0
        }
        
        // Wrap this in a dispatch_once block so it is only run once
        dispatch_once(&Static.token) {
            // Get the original selectors and method implementations, and swap them with our new method
            let originalSelector = #selector(UIViewController.viewDidLoad)
            let swizzledSelector = #selector(UIViewController.myViewDidLoad)
            
            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
            
            let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
            
            // class_addMethod can fail if used incorrectly or with invalid pointers, so check to make sure we were able to add the method to the lookup table successfully
            if didAddMethod {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }
    }
    
    // Our new viewDidLoad function
    // In this example, we are just logging the name of the function, but this can be used to run any custom code
    func myViewDidLoad() {
        // This is not recursive since we swapped the Selectors in initialize().
        // We cannot call super in an extension.
        self.myViewDidLoad()
        print(#function) // logs myViewDidLoad()
    }
}

スイフトスウィズルの基本

TestSwizzlingクラスでmethodOne()methodTwo()の実装を入れ替えてみましょう:

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())

スウィズルの基礎 - 目的-C

Objective-CのUIViewのinitWithFrame:メソッドをスウィズする例

static IMP original_initWithFrame;

+ (void)swizzleMethods {
    static BOOL swizzled = NO;
    if (!swizzled) {
        swizzled = YES;

        Method initWithFrameMethod =
            class_getInstanceMethod([UIView class], @selector(initWithFrame:));
        original_initWithFrame = method_setImplementation(
            initWithFrameMethod, (IMP)replacement_initWithFrame);
    }
}

static id replacement_initWithFrame(id self, SEL _cmd, CGRect rect) {
    
    // This will be called instead of the original initWithFrame method on UIView
    // Do here whatever you need... 

    // Bonus: This is how you would call the original initWithFrame method
    UIView *view =
        ((id (*)(id, SEL, CGRect))original_initWithFrame)(self, _cmd, rect);

    return view;
}


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow