Objective-C 런타임: 메서드 스왑 및 메시지 전달

1. 메시지 전달

Objective-C에서는 함수 호출을 "메시지 전달"이라고 표현합니다.

[object performAction];

위 코드는 object에게 performAction이라는 메시지를 보낸다고 볼 수 있습니다. 여기서 object는 메시지의 수신자(recevier)입니다.

1.1. 셀렉터(selector)

셀렉터는 메서드 이름을 고유한 식별자로 변환하는 역할을 합니다. 동일한 이름을 가진 모든 메서드는 동일한 셀렉터 값을 갖습니다.

1.2. ISA 포인터

모든 객체는 자신이 속한 클래스를 가리키는 ISA 포인터를 가지고 있습니다. 클래스 객체는 인스턴스 메서드만 저장하며, 메타클래스는 클래스 메서드를 저장합니다.

1.3. 메시지 전달 과정

  1. 런타임은 객체의 ISA 포인터를 통해 해당 객체가 어떤 클래스에 속하는지 확인합니다.
  2. 클래스의 디스패치 테이블에서 요청된 셀렉터와 일치하는 메서드를 찾습니다.
  3. 일치하는 메서드가 발견되면 이를 실행하고, 필요하면 수신자의 인스턴스 변수도 전달됩니다.
  4. 일치하는 메서드를 찾지 못하면 상속 체인을 따라 부모 클래스로 올라갑니다.
  5. 최종적으로 메서드를 찾지 못하면 메시지 전달 체인이 시작됩니다.

2. 메시지 전달 예외: objc_msgSendSuper()

objc_msgSendSuper()는 상위 클래스로부터 메서드를 찾아 실행합니다. 이는 일반적인 메시지 전달과 다르게 상속 체인에서 한 단계 위에서부터 검색을 시작합니다.

3. 메서드 존재 여부 확인: respondsToSelector:

respondsToSelector:는 특정 메서드가 객체의 상속 체인에 존재하는지 확인합니다. 그러나 [self respondsToSelector:][super respondsToSelector:]는 동일한 동작을 수행하기 때문에, 특정 클래스에서 메서드가 오버라이드되었는지 확인하려면 다른 방법이 필요합니다.

4. 메시지 전달 실패 시 처리

4.1. 동적 메서드 해결

메시지 전달이 실패했을 때, 런타임은 다음과 같은 메서드들을 호출하여 동적 메서드 해결을 시도합니다.

@implementation MyClass

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(dynamicAction)) {
        class_addMethod(self, sel, (IMP)dynamicImplementation, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void dynamicImplementation(id self, SEL _cmd) {
    NSLog(@"Dynamically resolved method executed.");
}

@end

4.2. 대체 수신자 지정

메시지를 다른 객체로 전달할 수 있습니다.

@implementation HandlerClass
- (void)handleMessage {
    NSLog(@"Message handled by alternate receiver.");
}
@end

@implementation MainClass
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(handleMessage)) {
        return [[HandlerClass alloc] init];
    }
    return nil;
}
@end

4.3. 전체 메시지 전달

더 복잡한 경우, NSInvocation을 사용하여 메시지를 완전히 재구성할 수 있습니다.

@implementation FinalHandler
- (void)finalAction {
    NSLog(@"Final handler processed the message.");
}
@end

@implementation SourceClass
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(finalAction)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    SEL sel = [invocation selector];
    if ([self.finalHandler respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.finalHandler];
    } else {
        [super forwardInvocation:invocation];
    }
}
@end

5. 메서드 교환

디스패치 테이블 내의 메서드 주소를 변경하여 메서드 교환이 가능합니다. 이를 통해 두 메서드의 기능을 서로 바꿀 수 있습니다.

태그: Objective-C runtime message-passing

7월 11일 22:14에 게시됨