ObjC, Swift 함수 호출 패턴

ObjC, Swift 함수 호출 패턴

2025년 11월 3일

Objective-C #

소스코드 #

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

 1// 헤더
 2// ChatBotMenuTableViewController.h
 3@interface ChatBotMenuTableViewController : UIViewController <UITableViewDataSource>
 4@property (nonatomic) NSInteger itemCount;
 5- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
 6+ (NSString *)classBanner;
 7@end
 8
 9
10// 실제 구현부
11// ChatBotMenuTableViewController.m
12@implementation ChatBotMenuTableViewController
13
14- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
15    return self.itemCount; // 인스턴스 메서드
16}
17
18+ (NSString *)classBanner {
19    return @"Hello";       // 클래스 메서드
20}
21
22@end

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

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

컴파일 #

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

1// 원래 호출방식
2NSInteger n = [vc tableView:tv numberOfRowsInSection:0];
3// 컴파일러가 내부적으로 변환
4NSInteger n = ((NSInteger (*)(id, SEL, UITableView *, NSInteger))
5               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

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

 1// obj_class* classlist[]
 2// classlist[0]
 3107de3c78 90 33 3c        addr       objc::class_t::_TtC9KakaoTalkP33_1C0AD136B0ECA
 4          08 01 00 
 5          00 00
 6// classlist[1]
 7107de3c80 80 33 0b        addr       objc::class_t::_TtC9KakaoTalk18KakaoTalkResour
 8          08 01 00 
 9          00 00
10107de3c88 a8 3f 3c        addr       objc::class_t::_TtC9KakaoTalk24WatchConnectivi
11          08 01 00 
12          00 00
13107de3c90 38 34 0b        addr       objc::class_t::_TtC9KakaoTalk15TalkMigration80
14          08 01 00 
15          00 00
16...

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

1struct objc_class {
2    struct objc_class *isa;        // 메타클래스
3    struct objc_class *superclass; // 슈퍼클래스
4    struct cache_t    cache;       // (SEL -> IMP) 캐시
5    void              *vtable;     // 현대 iOS에선 거의 0
6    uintptr_t         data;        // class_data_bits_t: 플래그 섞인 포인터
7};
8// data의 하위 3bit는 플래그이며, 
9// 플래그를 제거하면 파일에서는 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

시간날때 계속 분석하기

실행 #

 1typedef struct method_t {
 2    SEL sel;           // "tableView:numberOfRowsInSection:"
 3    const char *types; // 타입 인코딩: "q@:@@i" 대략 이런 식
 4    IMP imp;           // 실제 함수 포인터: id(*)(id,SEL, ...)
 5} method_t;
 6
 7typedef struct cache_t {
 8    // 해시 테이블 (sel → imp), 빠른 재호출을 위해 캐싱
 9    bucket_t *buckets;
10    mask_t mask;
11    uint32_t occupied;
12} cache_t;
13
14struct objc_class {
15    Class isa;            // 메타클래스 등
16    Class superclass;     // 슈퍼클래스
17    cache_t cache;        // 최근 조회한 메서드 캐시
18    class_data_bits_t bits; // method list 등으로 이어짐
19};

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

1// [obj sel:arg1 arg2]
2mov  x0, obj          // self
3adrp x1, sel_ref      // _cmd = SEL("sel:arg2:")
4add  x1, x1, :lo12:sel_ref
5mov  x2, arg1
6mov  x3, arg2
7bl   _objc_msgSend    // 런타임이 (class, SEL) → IMP 조회 후 점프
comments powered by Disqus