Swift Language
메서드 Swizzling
수색…
비고
Swift에서 메서드 swizzling을 사용할 때 클래스 / 메서드가 준수해야하는 두 가지 요구 사항이 있습니다.
- 클래스는
NSObject
를 확장해야합니다. - swizzle하고 싶은 기능은
dynamic
속성을 가져야합니다.
이것이 필요한 이유에 대한 자세한 설명은 Swift with Cocoa and Objective-C :
다이나믹 디스패치 요구
@objc
속성은 Swift API를 Objective-C 런타임에 노출하지만 속성, 메소드, 첨자 또는 이니셜 라이저의 동적 전달을 보장하지는 않습니다. Swift 컴파일러는 여전히 Objective-C 런타임을 우회하여 코드의 성능을 최적화하기 위해 멤버 액세스를 가상화하거나 인라인 할 수 있습니다 . 멤버 선언에dynamic
수정자를 표시하면 해당 멤버에 대한 액세스가 항상 동적으로 전달됩니다.dynamic
수정 자로 표시된 선언은 Objective-C 런타임을 사용하여 전달되기 때문에 암시 적으로@objc
속성으로 표시됩니다.동적 발송을 요구하는 것은 거의 필요하지 않습니다. 그러나 런타임에 API 구현을 바꾼다는 것을 알고 있으면
dynamic
수정자를 사용해야합니다 . 예를 들어, Objective-C 런타임의method_exchangeImplementations
함수를 사용하여 응용 프로그램이 실행되는 동안 메소드 구현을 스왑 아웃 할 수 있습니다. Swift 컴파일러가 메소드의 구현을 유도하거나 그것에 대한 가상화 된 액세스를 구현하면 새 구현이 사용되지 않습니다 .
모래밭
UIViewController 확장 및 viewDidLoad Swizzling
Objective-C에서 메서드 스위 즐링은 기존 선택기의 구현을 변경하는 프로세스입니다. Selector가 디스패치 테이블에 매핑되는 방식 또는 함수 또는 메서드에 대한 포인터의 테이블로 인해 가능합니다.
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())
Swizzling의 기본 - 목표 -C
UIView의 initWithFrame:
메서드를 swizzling하는 목적 - 목표
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;
}