STAGE 8 · 개발자리 iOS 로드맵

UNDERSTANDING THE ENGINE

🚗 엔진을 이해하는 드라이버 — "더 깊이 알고 싶다"

🛠 S1
🐢 S2
🍝 S3
💀 S4
🌐 S5
🧊 S6
🧪 S7
🔭 S8 ←

😤 마주치는 문제

왜 이렇게 동작하는지 근본 원리가 궁금합니다 / 시니어로 성장하고 싶습니다

🎯 배워서 해결

컴퓨터 구조, OS 원리, 컴파일러, 수학적 기초, 언어 이론

✅ 이 단계를 마친 당신은

CPU, OS, 컴파일러의 원리를 이해합니다. 왜 그렇게 동작하는지 설명할 수 있고, 새로운 기술이 나와도 본질을 꿰뚫습니다. 주니어를 가르칠 수 있고, 기술 의사결정의 근거를 댈 수 있습니다. 시니어 개발자입니다.

LEARNING PROGRESS 0 / 0
8-1 · 컴퓨터 구조
0 / 0
8-2 · 운영체제
0 / 0
8-3 · 컴파일러
0 / 0
CHAPTERS
8-1
💻
컴퓨터는 어떻게 코드를 실행하는가
CPU 바운드 vs I/O 바운드 · M1이 왜 빠른지
▾
💡 왜 여기서? 7개의 Stage를 거치며 iOS 개발의 전체를 경험했습니다. 이제 "왜 이렇게 동작하는가"라는 근본적인 질문이 생깁니다. CPU가 코드를 어떻게 실행하는지 알면 지금까지 배운 async/await, 메모리, 성능의 모든 것이 다시 보입니다.
CPU 구조
Fetch → Decode → Execute
명령어 실행 사이클의 3단계
  • Fetch: 메모리에서 다음 명령어를 가져옴 (PC 레지스터 사용)
  • Decode: 명령어를 해석해 어떤 연산인지 파악
  • Execute: ALU가 실제 연산 수행 후 결과를 레지스터에 저장
  • 이 사이클이 초당 수십억 번 반복됨 = 클럭 속도
ALU, 레지스터, 제어 장치
CPU의 3대 핵심 구성요소
  • ALU (Arithmetic Logic Unit): 덧셈·비교·AND/OR 등 실제 연산 담당
  • 레지스터: CPU 내부의 초고속 임시 저장소 (수십 개, 각 64bit)
  • 제어 장치: 명령어를 해석하고 다른 유닛에 신호를 보내는 지휘자
  • PC (Program Counter): 다음 실행할 명령어의 주소를 기억
클럭 속도, 파이프라이닝, Branch Prediction
CPU가 빨라지는 세 가지 방법
  • 클럭: 1GHz = 초당 10억 사이클. 높을수록 빠르지만 발열 증가
  • 파이프라이닝: Fetch/Decode/Execute를 동시에 → 세탁기처럼 병렬 처리
  • Branch Prediction: if문 결과를 미리 예측해 파이프라인 낭비 줄이기
  • 예측 실패 시 파이프라인 flush → 성능 패널티 (Spectre 취약점의 원인)
Apple Silicon — Unified Memory의 의미
M1이 왜 그렇게 빠른가
  • 기존 PC: CPU ↔ RAM ↔ GPU, 각각 분리된 메모리 → 데이터 복사 비용 발생
  • Unified Memory: CPU·GPU·Neural Engine이 하나의 메모리 풀 공유
  • Metal에서 CPU→GPU 데이터 전송이 제로 카피로 가능
  • SoC (System on Chip): CPU/GPU/메모리/I/O가 한 칩 → 저전력·고속
CPU 바운드 vs I/O 바운드
async/await가 필요한 근본 이유
  • CPU 바운드: 연산이 병목 → 멀티코어 병렬 처리로 해결 (DispatchQueue.concurrent)
  • I/O 바운드: 네트워크/디스크 대기가 병목 → async/await로 스레드 해방
  • async는 I/O 대기 중 스레드를 다른 일에 쓸 수 있게 해줌 (스레드 절약)
  • URLSession이 async인 이유: 네트워크 응답 대기가 전형적인 I/O 바운드
메모리 계층
레지스터 < L1 < L2 < RAM < SSD
속도/크기 트레이드오프
  • 레지스터: ~0.1ns, 수십 개 (bytes) — CPU 내부
  • L1 캐시: ~1ns, 32-128KB — 코어당 전용
  • L2 캐시: ~5ns, 256KB-1MB
  • RAM: ~100ns, GB 단위
  • SSD: ~100μs, TB 단위 → RAM보다 1000배 느림
캐시 히트 vs 미스
코드 접근 패턴이 성능을 바꾸는 이유
  • 캐시 히트: 찾는 데이터가 캐시에 있음 → 1ns 처리
  • 캐시 미스: RAM까지 가서 가져와야 함 → 100ns 대기
  • 캐시 미스율을 1% 낮추면 전체 성능이 크게 향상될 수 있음
  • iOS Instruments의 Cache Miss 지표가 성능 병목 원인이 되는 이유
공간/시간 지역성
배열이 연결 리스트보다 빠른 이유
  • 공간 지역성: 인접한 메모리를 연속 접근 → 한 번에 캐시라인(64byte) 로드
  • Array: 연속 메모리 → 공간 지역성 완벽 → 캐시 친화적
  • LinkedList: 포인터 추적 → 랜덤 접근 → 캐시 미스 폭발
  • 시간 지역성: 최근 쓴 데이터를 곧 다시 씀 → 루프 내 변수가 캐시에 유지
iOS Jetsam
메모리 압박 시 앱이 종료되는 메커니즘
  • Jetsam: iOS의 OOM killer, Android의 LMK와 유사
  • 메모리 부족 시 우선순위 낮은 앱부터 강제 종료 (SIGKILL)
  • Background 앱이 Suspended → Terminated 순으로 제거됨
  • didReceiveMemoryWarning으로 알림받고 캐시 해제 필요
가상 메모리
각 앱이 독립된 공간을 갖는 방법
가상 메모리의 존재 이유
  • 모든 프로세스가 같은 물리 메모리를 직접 쓰면 충돌 발생
  • 가상 주소 공간: 각 앱은 0~2^64 범위의 독립적인 주소를 씀
  • OS + MMU(Memory Management Unit)가 가상→물리 주소 변환
  • 한 앱의 버그가 다른 앱의 메모리를 침범할 수 없음
페이지와 페이지 테이블
가상→물리 주소 변환 메커니즘
  • 메모리를 고정 크기(보통 16KB on Apple Silicon) 단위=페이지로 나눔
  • 페이지 테이블: 가상 페이지 번호 → 물리 프레임 번호 매핑 테이블
  • TLB (Translation Lookaside Buffer): 페이지 테이블 캐시
  • 주소 변환: 가상주소 = 페이지번호 + 오프셋 → 물리주소로 변환
iOS Dirty vs Clean Memory
최적화의 핵심 개념
  • Clean Memory: 파일/실행파일에서 로드, 필요시 다시 로드 가능 → 스왑 가능
  • Dirty Memory: 앱이 직접 수정한 메모리 → 반드시 RAM에 유지
  • iOS는 스왑(Swap) 파티션이 없음 → Dirty Memory 최소화가 중요
  • 캐시 이미지를 NSCache로: 메모리 압박 시 자동 해제(Clean으로)
ASLR
주소 랜덤화와 보안 효과
  • ASLR (Address Space Layout Randomization): 실행 때마다 메모리 배치 랜덤화
  • 공격자가 "특정 주소에 악성코드 삽입" 전략을 쓸 수 없게 만듦
  • iOS는 ASLR + PIE (Position Independent Executable) 기본 적용
  • ROP (Return Oriented Programming) 공격 방어에도 기여
8-2
🏃
운영체제가 하는 일
GCD 설계 원리 · 백그라운드에서 앱이 죽는 이유
▾
💡 왜 여기서? CPU 구조를 알면 "그 위에서 OS가 무엇을 관리하는가"로 자연스럽게 연결됩니다. GCD의 QoS, 백그라운드 처리 한계, 스케줄러 — 지금까지 "그냥 그런 것"으로 받아들였던 것들의 근거가 여기서 보입니다.
프로세스 관리
앱 실행부터 종료까지
프로세스 생명주기 전체 흐름
  • fork(): 부모 프로세스를 복제해 새 프로세스 생성
  • exec(): 새 프로세스에 앱 바이너리를 로드
  • iOS에서는 fork/exec 직접 호출 불가, launchd(PID 1)가 관리
  • 종료: exit() → OS가 메모리/파일핸들 회수
스케줄링 알고리즘
어떤 프로세스가 CPU를 쓸 것인가
  • Round Robin: 시간 조각(time slice)씩 돌아가며 실행
  • Priority Scheduling: 우선순위 높은 것 먼저 — iOS QoS 기반
  • Multilevel Feedback Queue: 동작 패턴에 따라 큐를 이동
  • Context Switch: 현재 프로세스 상태 저장 → 다음 프로세스 복원 (비용 있음)
iOS QoS — .userInteractive에서 .background까지
GCD 큐의 우선순위가 스케줄러에 미치는 영향
  • .userInteractive: UI 업데이트, 애니메이션 — 최고 우선순위
  • .userInitiated: 사용자 요청 직접 응답 (버튼 탭 결과)
  • .default: 일반적인 작업
  • .utility: 진행 표시 없는 긴 작업 (다운로드)
  • .background: 사용자 모르게 실행, CPU 시간 최소 배분
iOS 앱 상태 전이
Foreground / Background / Suspended
  • Not Running → Active: 홈 화면 탭 → 앱 실행
  • Active → Background: 홈 버튼 → applicationDidEnterBackground()
  • Background → Suspended: 수 초 후 OS가 CPU 할당 중지
  • Suspended → Terminated: 메모리 부족 시 Jetsam이 SIGKILL
  • 앱은 Suspended 상태를 직접 감지할 수 없음 (알림 없음)
Background Task
백그라운드 처리 시간 한계와 요청 방법
  • beginBackgroundTask: 최대 ~30초 추가 실행 시간 확보
  • BGTaskScheduler: iOS 13+, 시스템이 적절한 시점에 작업 실행
  • BGProcessingTask: 긴 작업 (분 단위), 충전 중에 주로 실행
  • BGAppRefreshTask: 짧은 업데이트 (30초), 시스템이 빈도 조절
동기화 원리
Race Condition, Critical Section, Mutex
동시성 문제의 근본
  • Race Condition: 두 스레드가 동시에 같은 자원 수정 → 결과 예측 불가
  • Critical Section: 동시에 하나의 스레드만 실행해야 하는 코드 구간
  • Mutex (Mutual Exclusion): 락을 걸어 Critical Section 보호
  • Swift: NSLock, os_unfair_lock, @Synchronized 패턴
Semaphore — 동시 접근 개수 제한
Mutex의 일반화 버전
  • Semaphore(n): n개의 스레드가 동시에 접근 허용
  • Semaphore(1) = Mutex (Binary Semaphore)
  • wait(): 카운트 감소, 0이면 블록
  • signal(): 카운트 증가, 대기 중인 스레드 깨움
  • DispatchSemaphore: Swift에서 사용 (async 코드에서는 사용 지양)
Deadlock 발생 조건 4가지와 방지법
교착상태의 근본 원인
  • 상호 배제: 자원은 하나의 프로세스만 사용
  • 점유 대기: 자원 보유 중 다른 자원 대기
  • 비선점: 강제로 자원 빼앗기 불가
  • 순환 대기: A→B→C→A 형태의 원형 대기
  • 방지: 락 순서 고정, 타임아웃, 락 계층 구조
Actor 모델이 Mutex보다 나은 이유
Swift Concurrency의 설계 철학
  • Mutex: 수동으로 락/언락, 실수하면 Deadlock이나 Race Condition
  • Actor: 메시지 큐로 직렬화, 한 번에 하나의 작업만 실행 보장
  • 컴파일러가 Actor isolation 위반을 컴파일 에러로 잡아줌
  • @MainActor: UI 업데이트를 항상 메인 스레드로 보장
파일 시스템
APFS
Copy-on-Write, 스냅샷, 암호화
  • CoW (Copy-on-Write): 파일 복사 시 즉시 복사 안 함, 수정 시점에 분기
  • 스냅샷: CoW 덕분에 전체 파일시스템 상태를 거의 즉시 백업
  • Clones: 동일 파일 복사본이 공간 차지 없음 (같은 데이터 블록 공유)
  • 기본 AES-256 암호화, Secure Enclave과 연동
iOS 샌드박스
Documents / Library / Caches / tmp
  • Documents: 사용자 생성 파일, iCloud 백업 포함, iTunes 공유 가능
  • Library/Application Support: 앱 데이터, iCloud 백업 포함
  • Library/Caches: 재생성 가능한 캐시, 백업 제외, OS가 삭제 가능
  • tmp: 임시 파일, 앱 미실행 시 OS가 청소, 백업 제외
iCloud 백업 포함/제외 설정
사용자 저장 공간 절약과 심사 요구사항
  • isExcludedFromBackup: 재다운로드 가능한 파일은 백업 제외 필수
  • App Store 심사: 불필요한 파일을 Documents에 저장하면 리젝 사유
  • NSURLIsExcludedFromBackupKey로 개별 파일 설정
  • CoreData/Realm DB 파일 위치와 백업 정책을 명시적으로 설정
8-3
🔧
컴파일러와 언어 내부
Swift Macros 원리 · 빌드가 왜 느린지
▾
💡 왜 여기서? OS를 알면 "내 Swift 코드가 어떻게 기계어가 되는가"가 궁금해집니다. 컴파일러를 이해하면 타입 시스템, Macros, 빌드 최적화의 모든 설계 결정이 납득됩니다. 언어를 쓰는 사람에서 언어를 이해하는 사람으로 넘어가는 단계입니다.
Swift 컴파일 과정
소스코드 → 바이너리까지
Lexing → Parsing → AST → Semantic Analysis
  • Lexing: 소스 텍스트 → 토큰 스트림 (let, x, =, 5 등)
  • Parsing: 토큰 → AST (Abstract Syntax Tree, 추상 구문 트리)
  • Semantic Analysis: 타입 체크, 이름 해석, 문법 오류 검출
  • SIL 생성 → LLVM IR → 기계어 (아키텍처별 최적화)
SIL — Swift Intermediate Language
Swift만의 최적화 단계
  • SIL: Swift 고유의 중간 언어, LLVM IR로 가기 전 단계
  • ARC (Automatic Reference Counting) 최적화 여기서 수행
  • 불필요한 retain/release 쌍 제거, 함수 인라이닝
  • swiftc -emit-sil로 SIL 출력 확인 가능
LLVM → 기계어 생성
플랫폼 독립적 최적화의 마지막 단계
  • LLVM IR: 플랫폼 독립적인 중간 표현 (C/Rust/Swift 모두 사용)
  • LLVM 최적화: 루프 벡터화, 데드코드 제거, 상수 폴딩
  • 코드 생성: ARM64 (iOS) 또는 x86_64 (Simulator) 기계어
  • Link: 여러 .o 파일 + 프레임워크를 하나의 실행 바이너리로
Swift Macros
Macro = AST를 받아 AST를 반환하는 함수
컴파일 타임 코드 생성의 원리
  • Macro는 컴파일 타임에 실행, 런타임 오버헤드 없음
  • 입력: SwiftSyntax의 SyntaxNode (AST 노드)
  • 출력: 새로운 AST 노드 (생성된 코드)
  • C 매크로와 달리 타입 안전, 문맥 인식, 디버깅 가능
@Observable 내부에서 생성되는 코드
Macro 확장 결과 직접 확인하기
  • Xcode: 소스 에디터 → 우클릭 → "Expand Macro"로 확인
  • @Observable은 각 stored property에 _$observe 래퍼 생성
  • willSet/didSet 대신 _$observationRegistrar.access/withMutation 삽입
  • Observation framework의 ObservationRegistrar를 내부적으로 사용
SwiftSyntax로 직접 Macro 만들기
나만의 컴파일 타임 코드 생성
  • swift-syntax 패키지: AST 파싱/생성 라이브러리
  • MemberMacro: 타입에 멤버 추가 (@Observable 방식)
  • ExpressionMacro: 표현식 변환 (#stringify 방식)
  • 테스트: MacroTesting 패키지로 입출력 문자열 비교
빌드 최적화
타입 추론 비용
복잡한 표현식이 느린 이유
  • Swift 타입 추론은 제약 해결(Constraint Solving) 기반
  • 체이닝 (a.b().c().d()): 각 단계 타입 추론이 누적
  • 복잡한 배열 리터럴: 요소 타입 추론이 지수적으로 증가 가능
  • 해결: 중간 타입 명시 (let x: String = ...) → 추론 범위 제한
Build Timing Summary
느린 함수 찾기
  • Other Swift Flags에 추가: -Xfrontend -warn-long-function-bodies=100
  • 100ms 이상 걸리는 함수에 경고 표시
  • Product → Perform Action → Build With Timing Summary
  • Report Navigator에서 어떤 파일이 오래 걸리는지 확인
Explicit Return Type, Module Caching
빌드 시간 절반으로 줄이기
  • 명시적 리턴 타입: 함수마다 -> ReturnType 기재 → 추론 생략
  • 모듈화: 변경 없는 코드를 별도 모듈로 → 증분 빌드 효과 극대화
  • Whole Module Optimization: Release 빌드 최적화 (빌드는 느림, 실행은 빠름)
  • Derived Data 정리: 가끔 캐시 오염 → cmd+shift+k로 클린 빌드
언어 이론
타입 시스템 — 정적 vs 동적, 타입 추론의 원리
Swift가 왜 이렇게 설계됐는가
  • 정적 타입: 컴파일 시 타입 결정 → 런타임 오류 감소, 최적화 유리
  • 동적 타입: 런타임 타입 결정 → 유연성 높음 (Python, JS)
  • Hindley-Milner: Swift 타입 추론의 기반 알고리즘
  • Structural vs Nominal: Swift는 Nominal (이름 기반), 프로토콜로 유연성 확보
Optional이 왜 모나드인가
함수형 패러다임 관점의 Optional
  • 모나드: 값을 컨텍스트로 감싸고 체이닝 가능한 구조
  • Optional.map: Some(x)에 함수 적용, None이면 None 유지
  • Optional.flatMap: nil 전파 자동화 → 체이닝 안전하게
  • if let / guard let = 모나드에서 값 꺼내기 (바인딩)
Swift vs Kotlin vs Rust — 같은 문제를 다르게 푸는 방법
언어 설계 철학 비교
  • Null 안전: Swift Optional vs Kotlin ?, !! vs Rust Option<T>
  • 동시성: Swift Actor vs Kotlin Coroutine vs Rust Ownership+Send
  • 메모리: Swift ARC vs Kotlin GC vs Rust Borrow Checker
  • Rust: 컴파일 타임 메모리 안전 → 런타임 GC 없음, 임베디드 가능