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_* 형태의 세그먼트들을 확인할 수 있다.
__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 조회 후 점프