ObjC, Swift 함수 호출 패턴

Objective-C

소스코드

Objective-C는 언어를 직접 사용할때도 복잡한 구조로 작성해야한다.

// 헤더
// ChatBotMenuTableViewController.h
@interface ChatBotMenuTableViewController : UIViewController <UITableViewDataSource>
@property (nonatomic) NSInteger itemCount;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
+ (NSString *)classBanner;
@end


// 실제 구현부
// ChatBotMenuTableViewController.m
@implementation ChatBotMenuTableViewController

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.itemCount; // 인스턴스 메서드
}

+ (NSString *)classBanner {
    return @"Hello";       // 클래스 메서드
}

@end

간단하게 함수의 시그니쳐를 분석해보자.

  • - 는 인스턴스 메서드이며, + 는 클래스 메서드이다.
  • (NSInteger) 는 리턴 타입이다.
  • tableView:(UITableView*)tableView 는 첫번째 인자를 의미하고 셀렉터키워드:(타입)매개변수이름 형태이다.
  • numberOfRowsInSection:(NSInteger)section 도 마찬가지로 두번째 인자를 의미한다.
  • 셀렉터 키워드들을 모아두면 그게 함수 이름이 된다. tableView:numberOfRowsInSection:

컴파일

1. 컴파일러가 내부적으로 objc_msgSend 함수를 통해 selector를 호출하도록 변경한다.

// 원래 호출방식
NSInteger n = [vc tableView:tv numberOfRowsInSection:0];
// 컴파일러가 내부적으로 변환
NSInteger n = ((NSInteger (*)(id, SEL, UITableView *, NSInteger))
               objc_msgSend)(vc, @selector(tableView:numberOfRowsInSection:), tv, 0);

asm 으로는 x0:self, x1:selector 이후 실제 인자들이 x2부터 전달된다.

2. 컴파일러가 클래스들을 메타데이터 구조로 내보낸다.

View > open subview > Segments 를 확인하면 아래처럼 __objc_* 형태의 세그먼트들을 확인할 수 있다.

2a2662ac-951f-4c8c-a0aa-1cb2191dd396
2a2662ac-951f-4c8c-a0aa-1cb2191dd396

__objc_classlist 세그먼트에 클래스 포인터 배열을 확인할 수 있다.

// obj_class* classlist[]
// classlist[0]
107de3c78 90 33 3c        addr       objc::class_t::_TtC9KakaoTalkP33_1C0AD136B0ECA
          08 01 00 
          00 00
// classlist[1]
107de3c80 80 33 0b        addr       objc::class_t::_TtC9KakaoTalk18KakaoTalkResour
          08 01 00 
          00 00
107de3c88 a8 3f 3c        addr       objc::class_t::_TtC9KakaoTalk24WatchConnectivi
          08 01 00 
          00 00
107de3c90 38 34 0b        addr       objc::class_t::_TtC9KakaoTalk15TalkMigration80
          08 01 00 
          00 00
...

각각의 원소는 아래의 구조로 되어있다

struct objc_class {
    struct objc_class *isa;        // 메타클래스
    struct objc_class *superclass; // 슈퍼클래스
    struct cache_t    cache;       // (SEL -> IMP) 캐시
    void              *vtable;     // 현대 iOS에선 거의 0
    uintptr_t         data;        // class_data_bits_t: 플래그 섞인 포인터
};
// data의 하위 3bit는 플래그이며, 
// 플래그를 제거하면 파일에서는 class_ro_t, 런타임에 class_rw_t 포인터가 된다.

https://chatgpt.com/c/69098ced-bf94-8321-910c-07965c9b22ca

https://wwdcnotes.com/documentation/wwdcnotes/wwdc20-10163-advancements-in-the-objectivec-runtime

시간날때 계속 분석하기

실행

typedef struct method_t {
    SEL sel;           // "tableView:numberOfRowsInSection:"
    const char *types; // 타입 인코딩: "q@:@@i" 대략 이런 식
    IMP imp;           // 실제 함수 포인터: id(*)(id,SEL, ...)
} method_t;

typedef struct cache_t {
    // 해시 테이블 (sel → imp), 빠른 재호출을 위해 캐싱
    bucket_t *buckets;
    mask_t mask;
    uint32_t occupied;
} cache_t;

struct objc_class {
    Class isa;            // 메타클래스 등
    Class superclass;     // 슈퍼클래스
    cache_t cache;        // 최근 조회한 메서드 캐시
    class_data_bits_t bits; // method list 등으로 이어짐
};

런타임 메시지 디스패치 방식으로 동작한다.

// [obj sel:arg1 arg2]
mov  x0, obj          // self
adrp x1, sel_ref      // _cmd = SEL("sel:arg2:")
add  x1, x1, :lo12:sel_ref
mov  x2, arg1
mov  x3, arg2
bl   _objc_msgSend    // 런타임이 (class, SEL) → IMP 조회 후 점프

Comments

ESC
Type to search...