이 자료의 구조 — 깊이 사다리
기본기 인터뷰에서 떨어지는 전형적인 이유는 몰라서가 아니라 한 층에서 멈춰서입니다. "struct는 값 타입이고 class는 참조 타입입니다"는 맞는 말이지만 첫 번째 층일 뿐입니다. 좋은 면접관은 여기서 멈추지 않고 계속 내려갑니다 — 그래서 메모리에서는 뭐가 다른가요? → 그게 왜 성능 차이를 만들죠? → CPU 입장에서 무슨 일이 일어나는 건가요?
그래서 13개 주제를 전부 4단계로 파고드는 사다리로 재구성했습니다. 위에서 아래로 내려갈수록 깊어지고, 마지막 칸은 반드시 CS 원리에 닿습니다.
L4는 과시하려고 붙인 게 아닙니다. 실용적인 이유가 둘 있습니다.
① 기억이 압축된다 — @escaping, CoW, 셀 재사용, setNeedsLayout은 따로 외우면 항목 4개지만, "수명을 정적으로 알면 스택에 올릴 수 있다", "복사를 쓰기 시점까지 미룬다", "객체를 재활용해 할당을 없앤다", "계산을 무효화 플래그로 미룬다"로 보면 전부 지연·재활용이라는 한 가지 아이디어입니다.
② 모르는 질문에도 답할 수 있다 — 처음 듣는 API가 나와도 L4 층에서 추론이 됩니다.
각 문항 페이지는 사다리 4칸에 아래 세 가지가 붙습니다.
Swift와 iOS는 "실행해 봐야 아는 것"을 "코드를 보면 아는 것"으로 계속 옮겨 왔다.
nil은 Optional 타입으로, 데이터 경합은 Sendable·actor로, 클로저 수명은 @escaping으로, 레이아웃 시점은 무효화 플래그로 — 13개 주제는 전부 이 이동의 서로 다른 장면입니다. 답변할 때 이 축을 잡고 있으면 주제가 바뀌어도 논리가 흔들리지 않습니다.
① 트레이드오프를 말하지 않는다 — "ReactorKit이 좋습니다", "struct가 빠릅니다". 얻은 것만 말하면 유행을 따라간 것으로 봅니다.
② 측정 없이 최적화를 말한다 — "그래서 final을 붙였습니다"에 "얼마나 빨라졌나요?"가 따라옵니다.
③ 예외를 모른다 — "[weak self]는 항상 씁니다", "serial queue는 같은 스레드입니다". 규칙만 외운 티가 가장 크게 납니다.
주제 인덱스
Part 1 · 설계 판단
mutate/reduce 2단계 분리의 이유와 안 쓸 상황까지Part 2 · 타입과 실행 모델
Sendable 차이objc_msgSend. 진짜 비용은 조회가 아니라 최적화 차단!가 정당한 경우self.를 강제하는가count≠capacity, 슬라이스가 원본을 붙잡는 함정, O(1)이 더 느린 구간Part 3 · 동시성과 메모리
[weak self]가 불필요한 경우, 릭이 아닌 메모리 증가Part 4 · UIKit 실전
viewIsAppearing이 바꾼 것주제 사이의 연결
면접에서 강한 인상을 주는 건 한 주제의 답이 다른 주제로 이어질 때입니다. 12개는 따로 노는 항목이 아니라 아래처럼 엮여 있습니다.
L4 지도 — 각 주제가 닿는 CS 영역
이 표가 이 자료의 뼈대입니다. 왼쪽은 iOS 질문이고 오른쪽은 그 질문의 정체입니다. 면접에서 "그건 결국 ○○ 문제입니다"라고 한 층 내려가면, 그 순간 대화의 성격이 암기 확인에서 원리 대화로 바뀝니다.
| 주제 | L4 — 컴퓨터과학에서 이건 무슨 문제인가 |
|---|---|
| 01 아키텍처 | 밀리 머신 · fold(누적) · 이벤트 소싱 — mutate는 출력 함수 λ, reduce는 전이 함수 δ. 상태는 저장된 게 아니라 Mutation 스트림을 scan으로 접어 온 것 |
| 02 Swift | 타입 이론 · 정적 분석의 한계 — 대수적 데이터 타입(합/곱), 타입 건전성, 그리고 Rice의 정리가 말하는 "컴파일러가 원리적으로 못 잡는 것" |
| 03 POP/OOP | 다형성의 세 종류 · 딕셔너리 패싱 — 서브타입 / 파라메트릭 / 애드혹. witness table은 Haskell 타입클래스의 딕셔너리 패싱과 같은 구현 |
| 04 Struct/Class | 메모리 계층 · 공간 지역성 — 캐시 라인, 포인터 추격과 캐시 미스, 정렬과 패딩. CoW는 OS의 fork() 페이지 공유와 같은 아이디어 |
| 05 Dispatch | 간접 분기와 CPU 파이프라인 — 분기 타깃 예측(BTB), 예측 실패 시 파이프라인 플러시, 인라인 캐시 |
| 06 Optional | 합 타입 · 태그 압축 — Optional은 sum type. extra inhabitant는 포인터 태깅·NaN 박싱과 같은 "안 쓰는 비트 패턴 재활용" 기법 |
| 07 Thread Safety | 메모리 모델 · 캐시 일관성 · 상호 배제 — MESI, 재정렬, 임계 구역, 데드락 4조건(Coffman), actor 모델(Hewitt 1973) |
| 08 Lifecycle | 이벤트 루프 · 제어의 역전 — RunLoop는 mach 포트에서 블로킹 대기하는 커널 수준 이벤트 루프. 생명주기는 그 루프에 걸린 콜백 상태 기계 |
| 09 메모리 릭 | 도달 가능성과 그래프 사이클 — 순수 참조 카운팅은 지역 정보만 보므로 순환을 판정할 수 없다(부분 추적을 덧붙이면 가능 — Swift는 안 넣기로 택함). Jetsam은 OS의 메모리 압박 대응 |
| 10 Escaping | 이스케이프 분석 · 클로저 변환 — JVM·Go에도 있는 고전 최적화. "수명을 정적으로 알면 스택에 올릴 수 있다" |
| 11 Cell 최적화 | 객체 풀링 · 소프트 실시간 스케줄링 — 프레임 예산은 데드라인. 놓치면 결과가 늦는 게 아니라 틀린다 |
| 13 Collection | amortized 분석 · 해시 테이블 · 캐시 지역성 — 지수적 성장이 왜 평균 O(1)을 만드는지(등비급수), 그리고 O(1)이 O(n)보다 느린 구간이 존재하는 이유 |
| 12 Layout | 지연 평가 · 증분 계산 · 제약 해결 — 더티 플래그는 무효화 기반 증분 계산. Auto Layout은 Cassowary 제약 해결기 |
주제별 "이것만은" 한 줄
| 주제 | 이 한 줄만 정확히 말해도 통과선 |
|---|---|
| 01 아키텍처 | 아키텍처는 정답이 아니라 제약이고, 제약의 값은 팀 상황이 정한다 |
| 02 Swift | 런타임 오류를 타입으로 옮기는 것이 일관된 설계 방향이다 |
| 03 POP/OOP | 프로토콜 extension의 비요구사항 메서드는 오버라이드되지 않는다 |
| 04 Struct/Class | 성능이 아니라 복사냐 공유냐가 먼저다 |
| 05 Dispatch | 비용의 본질은 조회 한 번이 아니라 인라이닝 차단이다 |
| 06 Optional | "없음"이 정상 상태인지 실패인지 구분하면 throws와 갈린다 |
| 07 Thread Safety | Data race를 없애도 Race condition은 남는다 |
| 08 Lifecycle | viewDidLoad에서 크기는 아직 확정이 아니다 |
| 09 메모리 릭 | [weak self]는 self가 그 클로저를 저장할 때만 필요하다 |
| 10 Escaping | @escaping은 성능 옵션이 아니라 수명 계약이다 |
| 11 Cell 최적화 | cellForRowAt에서 프레임 예산을 쓰지 않는다 |
| 12 Layout | 무효화는 병합(coalescing)된다 — 그래서 기본은 예약이다 |
| 13 Collection | O(1)은 "커져도 안 늘어난다"는 뜻이지 "지금 빠르다"가 아니다 |
① 지연(laziness) — CoW, 더티 플래그, lazy 디코딩, estimatedRowHeight. 전부 "필요해질 때까지 미룬다".
② 정적 지식으로 동적 비용 없애기 — 정적 디스패치, 이스케이프 분석, Sendable 검사, exhaustive switch. 전부 "컴파일 타임에 알면 런타임에 안 해도 된다".
③ 공유를 줄여 추론 가능성 얻기 — 값 타입, actor 격리, 단방향 흐름, 순수 함수. 전부 "공유 가변 상태가 복잡도의 근원"이라는 같은 명제.