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