Swift Language
方法スウィズル
サーチ…
備考
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コンパイラがメソッドの実装をインライン化するか、またはそれに対する仮想化されたアクセスをインライン化した場合、 新しい実装は使用されません 。
リンク
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;
}