[Objective-C] hooking과 method swizzling

Swift와 Objective-C에서 Swizzling과 Hooking은 비슷한 기술이지만, 다른 목적과 구현 방식을 가지고 있다.

 

Swizzling

런타임에서 method 구현을 변경하는 것을 의미하며, 일반적으로 클래스의 method를 다른 method로 교체하는 것을 의미한다. 이를 통해 기존의 method 동작을 수정하거나 확장할 수 있다.

Swift와 Objective-C가 동적으로 동작하기 때문에, Swizzling이 가능하다. (정적으로 동작하는 C / C++과 같은 경우에는 method Swizzling이 불가능)

Swizzling은 주로 디버깅이나 런타임에서의 동작 변경에 사용된다.

+ (void)swizzleMethod {
        Class class = [self class];

        SEL originalSelector = @selector(setBackgroundColor:);
        SEL swizzledSelector = @selector(swizzledSetBackgroundColor:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
}

- (void)swizzledSetBackgroundColor:(UIColor *)backgroundColor {

    NSLog(@"before changed: %@", backgroundColor);

    // 본래 함수 실행
    [self swizzledSetBackgroundColor:backgroundColor];

    NSLog(@"after changed: %@", backgroundColor);
}



Hooking

Hooking은 호출 전후에 특정 코드를 주입하여 해당 동작을 변경하는 것을 의미한다. (함수 자체를 바꾸지는 않음)

Hooking은 보안 연구, 게임 수정, 시스템 트래픽 분석 등에 사용된다.

- (void)hookSetBackgroundColor {
    // 본래 method
    SEL originalSelector = @selector(setBackgroundColor:);
    Method originalMethod = class_getInstanceMethod([self class], originalSelector);

    // hooking ... ...
    IMP newImplementation = imp_implementationWithBlock(^void(id self, UIColor *backgroundColor) {
        NSLog(@"before changed: %@", backgroundColor);

        // 본래 함수 실행
        void (*originalImp)(id, SEL, UIColor *) = (void (*)(id, SEL, UIColor *))method_getImplementation(originalMethod);
        originalImp(self, originalSelector, backgroundColor);

        NSLog(@"after changed: %@", backgroundColor);
    });

    // Replace the original method implementation with the new implementation
    method_setImplementation(originalMethod, newImplementation);
}



ㅜㅜ 나는 둘이 왜 다른지 너무너무너무 궁금했는데 관련해서 나와있는 게 없어서 그냥 둘 다 따로따로 파본 후에 내가 비교한다………..


method Hooking과 Swizzling을 찾게 된 이유는 다음과 같다.

나는 배경색이 바뀌는 타이밍을 알고 싶었다.

그러기 위해서는! setBackground method를 hooking하여 method가 실행되기 이전과 이후에 log를 찍을 수 있겠당.


이걸로 삽질하다가 결국 다른 방식으로 해결했지만.. ㅜㅜ 일단 삽질한 시간이 아까우니까

~

!!! 기록용으로라도 남겨놓는다.

 

 

 

 

+ 240503 추가

이전에는 hooking이 뭔지 모른 채로 구현해서 잘 몰랐는데, method swizzling과 같이 사용하면 내가 원하는 효과를 낼 수 있었당. ㅎㅎ

관련 코드는 추후 추가하도록 하겠음 ....

 

 

활용 예시

- (void)hookTouchesEvent:(UIButton *)button {
    
    if (!didSwizzled) { // swizzle 되지 않았을 때만 실행
    // TouchesBegan 함수를 swizzle
        SEL originalBeganSelector = @selector(touchesBegan:withEvent:);
        Method originalBeganMethod = class_getInstanceMethod([UIButton class], originalBeganSelector);
        buttonBeganImp = method_getImplementation(originalBeganMethod);
        
        IMP originalBeganImp = method_getImplementation(originalBeganMethod);
        IMP newBeganImplementation = imp_implementationWithBlock(^(id self, NSSet<UITouch *> *touches, UIEvent *event) {
            NSLog(@"================================== touchesBegan");
            [touches.anyObject locationInView:touches.anyObject.view];
            
            // 원본 메서드 호출
            void (*originalBeganImpFunc)(id, SEL, NSSet<UITouch *> *, UIEvent *) = (void (*)(id, SEL, NSSet<UITouch *> *, UIEvent *))originalBeganImp;
            originalBeganImpFunc(self, originalBeganSelector, touches, event);
        });
        
        method_setImplementation(originalBeganMethod, newBeganImplementation);
        
        
    // TouchesEnded 함수를 swizzle
        SEL originalEndedSelector = @selector(touchesEnded:withEvent:);
        Method originalEndedMethod = class_getInstanceMethod([UIButton class], originalEndedSelector);
        buttonEndedImp = method_getImplementation(originalEndedMethod);
        
        IMP originalEndedImp = method_getImplementation(originalEndedMethod);
        IMP newEndedImplementation = imp_implementationWithBlock(^(id self, NSSet<UITouch *> *touches, UIEvent *event) {
            NSLog(@"================================== touchesEnded");
            [touches.anyObject locationInView:touches.anyObject.view];
            
            // 원본 메서드 호출
            void (*originalEndedImpFunc)(id, SEL, NSSet<UITouch *> *, UIEvent *) = (void (*)(id, SEL, NSSet<UITouch *> *, UIEvent *))originalEndedImp;
            originalEndedImpFunc(self, originalEndedSelector, touches, event);
        });
        
        method_setImplementation(originalEndedMethod, newEndedImplementation);
        
    // TouchesCanceled 함수를 swizzle
        SEL originalCancelledSelector = @selector(touchesCancelled:withEvent:);
        Method originalCancelledMethod = class_getInstanceMethod([UIButton class], originalCancelledSelector);
        buttonCancelledImp = method_getImplementation(originalCancelledMethod);
        
        IMP originalCancelledImp = method_getImplementation(originalCancelledMethod);
        IMP newCancelledImplementation = imp_implementationWithBlock(^(id self, NSSet<UITouch *> *touches, UIEvent *event) {
            NSLog(@"================================== touchesCancelled");
            [touches.anyObject locationInView:touches.anyObject.view];
            
            // 원본 메서드 호출
            void (*originalCancelledImpFunc)(id, SEL, NSSet<UITouch *> *, UIEvent *) = (void (*)(id, SEL, NSSet<UITouch *> *, UIEvent *))originalCancelledImp;
            originalCancelledImpFunc(self, originalCancelledSelector, touches, event);
        });
        
        // Replace the original touchesCancelled method implementation with the new implementation
        method_setImplementation(originalCancelledMethod, newCancelledImplementation);
        
        didSwizzled = YES;
    }
}
- (void)unHookTouchesEvents:(UIButton *)button {
    
    SEL originalBeganSelector = @selector(touchesBegan:withEvent:);
    Method originalBeganMethod = class_getInstanceMethod([UIButton class], originalBeganSelector);
    method_setImplementation(originalBeganMethod, buttonBeganImp);
    
    SEL originalEndedSelector = @selector(touchesEnded:withEvent:);
    Method originalEndedMethod = class_getInstanceMethod([UIButton class], originalEndedSelector);
    method_setImplementation(originalEndedMethod, buttonEndedImp);
    
    SEL originalCancelledSelector = @selector(touchesCancelled:withEvent:);
    Method originalCancelledMethod = class_getInstanceMethod([UIButton class], originalCancelledSelector);
    method_setImplementation(originalCancelledMethod, buttonCancelledImp);
    
}

 

해당 뷰가 여러 번 호출될 수 있어서 unhook method를 만들어 적용해 주었다. (그렇게 하지 않으면 바뀐 Method가 계속 중첩되어 호출될 때마다 로그가 하나씩 더 추가되어 나왔다. :3 ........)

 

자세한 설명은 코드 안에!

 

'iOS > Objective-C' 카테고리의 다른 글

Arithmetic Overflow in Swift & Objective-C  (0) 2025.04.07
Objective-C 기초 문법 정리  (2) 2024.02.15