iOS 및 Android 개발에서의 Method Swizzling: 핵심 개념, 기법 및 작동 원리

저자: IT Sectr 게시일: 2026-05-17 읽는 시간: 9 분

Method Swizzling은 실행 중에 두 클래스 메서드의 구현을 서로 바꾸는 런타임 기법입니다. 서브클래스를 만들거나 소스 코드를 수정하지 않고도 시스템 메서드의 동작을 재정의하거나 보완할 수 있습니다. 이 기법은 Objective-C를 사용한 iOS 개발에서 가장 많이 활용되었지만, Kotlin/Android에서도 리플렉션을 통해 유사한 방식이 존재합니다. NSHipster Guide by Mattt, 2024에 따르면, swizzling은 Objective-C Runtime에서 가장 강력하면서도 가장 위험한 메커니즘 중 하나입니다.

핵심 요점

  • Method Swizzling — sel_registerName과 method_exchangeImplementations를 통해 런타임에서 두 Objective-C 메서드의 구현을 교환.
  • Objective-C Runtime은 objc_msgSend와 디스패치 테이블을 통한 동적 디스패치로 swizzling을 가능하게 함.
  • Android에서의 Swizzling — Java Reflection을 통한 dex 파일의 구현 교체 또는 Gradle Transform API를 통해 구현.
  • Swizzling의 위험 — 라이브러리 간 충돌, iOS 업데이트와의 비호환성, 메서드 시그니처 변경 시 크래시.
  • 안전한 swizzling — dispatch_once, 원자성, swizzled 메서드 내에서 원래 구현 호출이 필요.

Method Swizzling이란?

Method Swizzling은 두 Objective-C 메서드의 구현을 서로 바꾸는 런타임 기법입니다. Swizzling 후 originalSelector를 호출하면 swizzledSelector의 코드가 실행되고, 그 반대도 마찬가지입니다. 이는 각 셀렉터(SEL)가 디스패치 테이블을 통해 구현(IMP)과 연결되는 Objective-C Runtime의 아키텍처 덕분에 가능합니다. 이 테이블은 실행 중에 수정할 수 있습니다.

“swizzling”이라는 용어는 2000년대 초 Cocoa 개발자 커뮤니티에서 도입되었습니다. 이 기법은 AFNetworking(로딩 추적을 위한 UIWebView swizzling), Aspects(swizzling 기반 AOP 프레임워크), FLEX(시스템 메서드를 swizzle하여 검사하는 디버깅 도구)와 같은 라이브러리 덕분에 널리 알려지게 되었습니다. 오늘날 swizzling은 대부분의 iOS 애플리케이션에서 모니터링 및 분석 라이브러리를 통해 암시적으로 사용됩니다.

Swizzling의 중요한 특성은 전역성입니다. 구현 교체는 인스턴스 수준이 아닌 클래스 수준에서 발생합니다. 라이브러리가 UIViewController.viewDidLoad 메서드를 swizzle하면 시스템 인스턴스를 포함하여 애플리케이션의 모든 UIViewController 인스턴스에 영향을 미칩니다. 이것은 swizzling의 강점(한 줄의 코드로 전체 애플리케이션의 동작을 변경)이자 버그의 주요 원인이기도 합니다.

Objective-C에서 Method Swizzling 작동 방식

Objective-C Runtime은 각 클래스에 디스패치 테이블을 저장합니다. 키가 SEL(메서드 식별자)이고 값이 IMP(구현 함수에 대한 포인터)인 딕셔너리입니다. 애플리케이션이 객체에 메시지를 보내면 objc_msgSend가 이 테이블을 선형 검색합니다. Method Swizzling은 한 SEL의 IMP를 다른 SEL의 IMP로 교체하여 호출을 리디렉션합니다.

objective-c
// 안전한 method swizzling 구현
@implementation NSObject (SafeSwizzle)

+ (void)swizzleClassMethod:(SEL)original
                  with:(SEL)swizzled {
    Class cls = [self class];
    SEL originalSel = original;
    SEL swizzledSel = swizzled;

    Method originalMethod = class_getInstanceMethod(cls, originalSel);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel);

    method_exchangeImplementations(originalMethod, swizzledMethod);
}

@end

핵심 함수는 method_exchangeImplementations(Method, Method)입니다. 이 함수는 두 Method 객체의 IMP를 원자적으로 교환합니다. 호출 후 클래스의 디스패치 테이블이 수정됩니다. original을 호출하면 swizzled 코드가 실행되고, swizzled를 호출하면 original 코드가 실행됩니다. SafeSwizzle 카테고리는 이 메서드를 모든 NSObject에 추가하여 모든 클래스가 swizzling을 수행할 수 있게 합니다.

안전한 swizzling 구현은 swizzled 버전 내에서 원래 구현을 호출해야 합니다. 그렇지 않으면 메서드의 원래 동작이 영구적으로 손실됩니다. 올바른 패턴은 교체 전에 원래 IMP를 저장하고 swizzled 메서드에서 호출하는 것입니다:

objective-c
// 원래 구현 호출을 포함한 Swizzling
- (void)swizzled_viewDidLoad {
    // 1. 원래 구현 호출
    [self swizzled_viewDidLoad];

    // 2. 원래 호출 후 추가 로직
    NSLog("viewDidLoad 실행됨, swizzling 활성");
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleClassMethod:@selector(viewDidLoad)
                            with:@selector(swizzled_viewDidLoad)];
    });
}

dispatch_once는 swizzling이 애플리케이션 수명 동안 정확히 한 번만 실행되도록 보장합니다. 동일한 메서드를 다시 swizzling하면 무한 재귀가 발생합니다. swizzled 메서드가 자신을 호출하게 됩니다. +load는 클래스가 런타임에 로드될 때 호출됩니다. 이는 메인 애플리케이션 코드보다 먼저 실행되는 swizzling의 안전한 지점입니다.

디스패치 테이블의 구조

Objective-C 클래스의 디스패치 테이블은 SEL, IMP 및 반환 타입을 포함하는 method_t 구조체의 배열입니다. method_exchangeImplementations는 단순히 이 테이블에서 두 IMP 포인터를 교환합니다. 중요한 점: swizzling은 클래스 수준에서만 작동하며 프로토콜 수준에서는 작동하지 않습니다. 메서드가 프로토콜에 정의되었지만 구현되지 않은 경우, 디스패치 테이블에는 swizzling을 위한 항목이 없습니다.

성능에 미치는 영향

Swizzling 오버헤드는 최소화됩니다. 디스패치 테이블에서 두 IMP 포인터를 교환하는 데는 몇 나노초가 걸립니다. Swizzling 후에도 메서드 디스패치는 느려지지 않습니다. objc_msgSend는 swizzling 전과 동일한 O(1) 시간에 IMP를 찾습니다. 유일한 추가 작업은 교체 후 첫 번째 호출 시 메서드 캐시 확인입니다. Apple Performance Team 데이터에 따르면 swizzling은 애플리케이션 성능에 영향을 미치지 않습니다.

iOS에서 Method Swizzling 활용 사례

Method Swizzling은 세 가지 주요 시나리오에서 사용됩니다: 모니터링 및 분석(자동 이벤트 전송을 위한 viewDidLoad, viewDidAppear 추적), AOP 인터셉션(모든 메서드 호출의 매개변수 로깅), 그리고 핫픽스(JSPatch와 같은 라이브러리를 통한 App Store Review 없이 프로덕션 버그 수정)입니다.

  • 자동 분석 — 각 컨트롤러에서 코드 중복 없이 화면 보기 이벤트를 전송하기 위한 UIViewController.viewDidAppear swizzling.
  • 네트워크 요청 로깅 — 서드파티 라이브러리를 포함한 모든 HTTP 요청을 추적하기 위한 NSURLSession.resume swizzling.
  • AOP(관점 지향 프로그래밍) — Aspects 라이브러리가 메서드를 swizzle하고 원래 호출 전/후/대신 코드 블록을 실행.
  • 핫픽스 — 애플리케이션을 다시 빌드하지 않고 버그가 있는 메서드의 구현을 수정된 것으로 교체(2020년부터 App Review에서 금지).
  • 테스트 및 목 — OCMock이 단위 테스트에서 메서드를 목 구현으로 교체하기 위해 swizzling 사용.

이러한 각 시나리오는 swizzling이 중앙에서 적용되기 때문에 작동합니다. 분석 라이브러리가 +load에서 한 번 swizzling을 수행하면 애플리케이션의 모든 UIViewController 인스턴스가 이벤트를 전송하기 시작합니다. 개발자는 각 컨트롤러에 코드를 추가할 필요가 없습니다. 이는 중복과 오류 위험을 줄입니다.

Android에서 Method Swizzling: 리플렉션과 바이트코드 조작

Android에서는 고전적인 Objective-C 의미의 method swizzling이 불가능합니다. Java/Kotlin은 vtable을 통한 정적 디스패치를 사용합니다. 그러나 유사한 효과를 달성하는 메커니즘이 존재합니다: 런타임 구현 교체를 위한 Java Reflection과 빌드 시 바이트코드 수정을 위한 Gradle Transform API / ASM입니다.

kotlin
// Android에서 reflection + companion object를 통한 Swizzling
class Logger {
    companion object {
        var originalImpl: (() -> Unit)? = null
    }

    fun log() {
        println("원래 로그")
    }
}

// 리플렉션을 통한 런타임 구현 교체
fun swizzleLog() {
    val originalMethod = Logger::class.java
        .getDeclaredMethod("log")
    originalMethod.isAccessible = true

    Logger.originalImpl = {
        originalMethod.invoke(Logger())
    }

    // 인라인 함수를 통한 대체
    println("swizzled: 로그 가로채짐")
}

이 코드는 Java Reflection을 통해 log() 메서드의 동작을 교체합니다. getDeclaredMethod는 비공개 구현에 접근하고, isAccessible은 접근 검사를 비활성화합니다. log()를 직접 호출하는 대신 추가 로직을 실행하는 래퍼가 호출됩니다. 그러나 Android는 JIT를 통해 핫 메서드를 최적화하므로, 리플렉션은 이미 컴파일된 AOT 세그먼트에서 작동하지 않을 수 있습니다.

더 신뢰할 수 있는 접근 방식은 ASM 라이브러리와 함께 Gradle Transform API 또는 AGP(Android Gradle Plugin)를 통한 바이트코드 조작입니다. 바이트코드 수정은 컴파일 시에 수행됩니다. ASM은 클래스의 각 메서드에 호출을 추가합니다. 코드 커버리지 도구(JaCoCo)와 성능 모니터링 도구(Firebase Performance Monitoring)가 이렇게 작동합니다.

Method Swizzling의 위험과 모범 사례

Method Swizzling은 고위험 기법입니다. 라이브러리 간 충돌: 두 라이브러리가 동일한 메서드를 swizzle하면 실행 순서가 보장되지 않습니다. iOS 업데이트와의 비호환성: Apple이 새 iOS 버전에서 시그니처를 변경하거나 메서드를 제거하면 swizzling이 크래시를 유발합니다. 코드 내 가시성 부족: swizzling은 클래스 구현에서 보이지 않아 디버깅이 어렵습니다.

위험설명완화
라이브러리 충돌두 라이브러리가 viewDidAppear를 swizzle하면 하나가 다른 하나를 망가뜨림class_getInstanceMethod로 메서드가 이미 swizzled되었는지 확인
재귀동일한 메서드를 다시 swizzling하면 무한 루프 발생항상 dispatch_once 사용
시그니처 변경Apple이 새 iOS에서 메서드 시그니처 변경 — IMP 불일치지원되는 모든 iOS 버전에서 테스트
비가시성Swizzling이 Xcode 호출 스택에 나타나지 않음코드의 모든 swizzling 작업 문서화
App ReviewApple이 문서화되지 않은 swizzling이 있는 앱을 거부공개 API만 사용하고 목적 문서화

안전한 swizzling을 위한 모범 사례는 다음과 같습니다: 항상 원래 구현 호출, dispatch_once를 통해 +load에서 엄격하게 swizzling 수행, swizzled 메서드에 접두사(예: s_originalMethodName) 사용, 각 swizzling 작업을 목적과 함께 문서화. Aspects 라이브러리는 원래 메서드 전/후에 블록을 체인 실행하여 충돌 문제를 해결합니다.

현대 개발에서 Method Swizzling의 대안

Method swizzling의 대안은 예측 가능성과 안전성 때문에 프로덕션 코드에 선호됩니다. 델리게이트와 프로토콜(UIApplicationDelegate, UITableViewDelegate)은 런타임을 수정하지 않고 명시적인 확장 지점을 제공합니다. 서브클래싱 — viewDidAppear를 재정의하는 UIViewController의 서브클래스 생성 — 은 예측 가능하게 작동하며 충돌이 없습니다.

SwiftUI와 Combine은 swizzling의 필요성을 제거합니다. 모디파이어(onAppear, onChange)는 메서드를 재정의하지 않고 선언적으로 동작을 추가합니다. Android Jetpack Compose에서는 이펙트(LaunchedEffect, SideEffect)와 모디파이어를 통해 동일한 효과를 얻습니다. AOP 프레임워크(Android용 AspectJ, iOS용 InterposeKit)는 컴파일 타임 위빙을 통한 안전한 대안을 제공합니다.

Apple WWDC 2024 데이터에 따르면, Swift 런타임은 언어 수준에서 method swizzling을 지원하지 않습니다. @objc dynamic 메서드만 Objective-C Runtime을 통해 swizzle될 수 있습니다. @objc를 사용하지 않는 Swift 애플리케이션은 서드파티 라이브러리에 의한 우발적 swizzling으로부터 완전히 보호됩니다. 이로 인해 Swift는 더 안전해지지만 런타임 계측 기능이 제한됩니다.

SwiftUI와 Compose의 선언적 대안

SwiftUI 모디파이어(onAppear, onChange, onReceive)와 Jetpack Compose 이펙트(LaunchedEffect, SideEffect, DisposableEffect)는 UI 작업에서 swizzling을 완전히 대체합니다. 이들은 디스패치 테이블을 수정하지 않고도 횡단 관심사를 추가하는 선언적이고 예측 가능하며 테스트 가능한 방법을 제공합니다. 새로운 프로젝트에서 Apple과 Google은 런타임 인터셉션 대신 이 접근 방식을 권장합니다.

자주 묻는 질문

Method Swizzling은 프로덕션에 안전한가요?

Method Swizzling은 규칙을 따를 경우 프로덕션에서 허용됩니다: dispatch_once를 통한 단일 실행, 원래 구현 호출, 모든 iOS 버전에서 테스트, 문서화. 간단한 작업의 경우 델리게이트나 서브클래싱을 사용하는 것이 좋습니다. 프로덕션에서의 Swizzling은 모니터링 및 분석 라이브러리에 적합합니다.

Swizzling과 AOP의 차이점은 무엇인가요?

Method Swizzling은 디스패치 테이블에서 IMP를 교체하는 특정 기법입니다. AOP(관점 지향 프로그래밍)는 swizzling이 메커니즘 중 하나로 사용될 수 있는 패러다임입니다. AOP에는 컴파일 타임 위빙(AspectJ), 프록시 기반 인터셉션(Spring AOP), 코드 생성도 포함됩니다.

Swizzling으로 인한 문제를 디버깅하는 방법은?

모든 메시지를 추적하려면 objc_msgSend에 중단점을 설정하세요. 클래스 이름을 조건으로 method_exchangeImplementations에 기호 중단점을 추가하세요. FLEX 도구는 클래스의 어떤 메서드가 swizzled되었는지 표시합니다. 체계적인 확인을 위해 클래스의 디스패치 테이블을 출력하는 lldb 스크립트를 사용하세요.

Swizzling이 Swift에서 작동하나요?

Swift는 언어 수준에서 swizzling을 지원하지 않습니다. Method Swizzling은 @objc dynamic으로 표시된 메서드에서만 작동하며, 이는 Objective-C Runtime을 통해 컴파일됩니다. 순수 Swift 메서드(@objc 없음)는 정적 디스패치를 사용하며 swizzle될 수 없습니다. 해당 디스패치 테이블은 수정을 위해 접근할 수 없습니다.

어떤 iOS 라이브러리가 Swizzling을 사용하나요?

Firebase Analytics(자동 화면 추적을 위한 viewDidAppear swizzling), Amplitude, Mixpanel, FLEX(UI 검사), OHHTTPStubs(네트워크 요청 모킹), Aspects(AOP 프레임워크). 모두 원래 구현 호출과 함께 dispatch_once를 통해 +load에서 swizzling을 수행합니다.

요약

  • Method Swizzling — method_exchangeImplementations를 통해 Objective-C Runtime 디스패치 테이블에서 두 메서드의 IMP를 교환.
  • dispatch_once는 재-swizzling과 재귀를 방지하기 위해 필수.
  • 원래 구현 호출은 swizzled 메서드 내에서 필수 안전 규칙.
  • Android에서는 swizzling이 Gradle Transform / ASM을 통한 리플렉션 또는 바이트코드 조작으로 대체됨.
  • 위험 — 라이브러리 충돌, iOS 버전 비호환성, 디버거에서 비가시성, 핫픽스에 대한 App Review 금지.
  • 대안 — 델리게이트, 서브클래싱, SwiftUI 모디파이어, Jetpack Compose 이펙트.
  • @objc dynamic이 없는 Swift 메서드는 swizzling으로부터 보호되어 안정성은 향상되지만 런타임 계측이 제한됨.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기