수색…


비고

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 컴파일러가 메소드의 구현을 유도하거나 그것에 대한 가상화 된 액세스를 구현하면 새 구현이 사용되지 않습니다 .

모래밭

Objective-C 런타임 참조

NSHipster에서 메서드 Swizzling

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


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow