← 홈으로 돌아가기

📝 Daily Learning Log (2026-06-08 ~ 06-14)

주간 학습일지 · 2026-06-08 ~ 06-14

2026-06-09 (월)

✅ 학습 완료

🔄 다음에 할 일

  • Swift Dispatch 문서 구조 개선 (템플릿 적용)

2026-06-10 (화)

✅ 학습 완료

📚 참고 자료

🔄 다음에 할 일

  • Swift Dispatch 문서 내용 보강 (VWT, PWT 상세 설명)

2026-06-11 (수)

✅ 학습 완료

💡 중요한 포인트

Existential Container 비유:
  • 택배 상자 = Existential Container (고정 크기 40 bytes)
  • 상자 공간 = Value Buffer (24 bytes)
  • "취급 주의" 스티커 = VWT (어떻게 복사/삭제하나?)
  • "사용 설명서" 스티커 = PWT (draw() 버튼은 어디?)

작은 물건 (≤ 24 bytes)은 상자에 직접 넣고, 큰 물건 (> 24 bytes)은 창고(힙)에 보관하고 주소표만 넣는다.

📕 오답노트

class에 let만 있으면 Sendable 아닌가? (왜 final이 필요?)

  • let만으론 부족: ① let의 타입도 Sendable이어야 하고 ② class는 final이어야 한다
  • final이 필요한 이유: class는 상속되므로, 하위 클래스가 var를 추가하고 부모 타입으로 위장해 넘기면 mutable 상태가 도메인을 넘는다
  • → final이 상속을 막아 "이 타입의 모든 객체는 불변"을 컴파일러가 확정. struct/enum은 상속이 없어 이 문제 자체가 없다
  • non-final class + Sendable = 컴파일 에러 (조심하라는 경고가 아님). @unchecked Sendable로만 우회

struct도 내부 var면 Sendable 안 되는 거 아냐?

  • 아니다! struct/enum은 var가 있어도 Sendable이 된다 (값 타입은 전달 시 복사되어 각자 독립)
  • 진짜 조건은 "멤버·associated value의 타입이 Sendable인가" 뿐
  • let 강제는 class에만 해당 — class는 참조 공유라 불변화가 필요

클로저가 값 바꿀까 봐 @Sendable을 붙이는 건가?

  • 방향이 거꾸로: @Sendable은 막는 장치가 아니라 "이 클로저는 도메인 경계를 넘는다"는 표식
  • 표식이 붙으면 컴파일러가 위험한 캡처(외부 var 변경, non-Sendable 캡처)를 검사해 막아줌
  • 보통 Task.detached 같은 API가 @Sendable을 요구해 자동 적용됨
  • class의 내부 var는 let box로 잡아도 참조 복사라 같은 객체 공유 → 못 넘김 (var 값만 let으로 복사하거나 타입을 Sendable로)

Boxing은 런타임에만 발생하는가?

  • 컴파일 타임: 컴파일러가 "여기에 Boxing 필요"라고 판단하고 코드 삽입
  • 런타임: 실제로 Container에 값을 복사하는 실행
  • → 컴파일러가 미리 알고 준비, 실제 실행은 런타임

"구체 타입 → Existential Container 변환"이 무슨 뜻?

  • 구체 타입: Circle, Point처럼 실제 타입 이름이 있는 것
  • 변환 (= Boxing): Circle (8 bytes) → Container (40 bytes)로 형태 변화
  • 타입도 Circleany Drawable로 바뀜
  • → 책상 위의 책을 택배 상자에 넣는 것과 같음

Unboxing은 타입 캐스팅할 때만?

  • 아니다! 메서드 호출할 때도 Unboxing 발생
  • Case 1: drawable.draw() → PWT 조회 → 값 꺼내기 → 메서드 실행
  • Case 2: as? Circle → 런타임 타입 체킹 → 값 추출
  • → 둘 다 Unboxing, 둘 다 런타임 오버헤드

Existential Container는 항상 힙에?

  • 아니다! 기본적으로 스택에 저장됨
  • 일반 변수: let drawable: any Drawable = ... → 스택 ✅
  • 클래스 프로퍼티: class VM { var drawable } → 힙 (클래스가 힙에 있으므로)
  • escaping 클로저 캡처 → 힙으로 이동
  • → Container 위치 ≠ Value Buffer 값 위치 (별개 개념)

Inline = 함수 인라이닝?

  • 아니다! 여기서 Inline은 "직접 저장"이라는 뜻
  • Inline 저장: 값 그 자체를 Value Buffer에 직접 넣음 (≤ 24 bytes)
  • Non-Inline: 포인터만 저장, 실제 값은 힙에 (> 24 bytes)
  • → 함수 인라이닝(코드 삽입)과는 다른 개념
  • → 편지봉투에 메모지 직접 넣기 vs 창고 주소표만 넣기

캐시 미스가 뭐길래 10-30배 느려?

  • CPU 캐시: 메모리보다 훨씬 빠른 임시 저장소
  • 캐시 히트: 데이터가 캐시에 있음 (~1-10 사이클) ✅
  • 캐시 미스: 캐시에 없어서 메인 메모리까지 가야 함 (~100-300 사이클) ❌
  • → Inline 저장: 연속 메모리 → 한 번에 캐시 로드 → 캐시 히트
  • → Non-Inline: 메모리 점프 (스택 → 힙) → 캐시 미스 가능성

Array는 24 bytes 이하니까 inline 저장되면 빠르다?

  • 절반만 맞다! Array 구조체는 inline 저장되지만, 요소는 항상 힙에 있음
  • Array 구조: buffer pointer (힙 가리킴) + count + capacity ≈ 24 bytes
  • 구조체는 Value Buffer에 inline 저장 가능
  • 하지만 실제 배열 요소 [1.0, 2.0, 3.0]은 항상 힙에 저장됨
  • → 결국 포인터를 따라가야 하므로 Non-Inline처럼 동작

🆕 Vtable이 뭔가?

  • Virtual Table = Class의 메서드 주소들을 담은 테이블
  • 각 Class마다 자신의 vtable을 가짐 (Animal's vtable, Dog's vtable)
  • 런타임에 "어떤 메서드를 호출할지" 결정하는 Dynamic Dispatch 메커니즘
  • 호출 과정: 인스턴스의 vtable pointer → vtable에서 메서드 주소 찾기 → 점프
  • → 다형성(Polymorphism)을 구현하기 위한 핵심 구조

🆕 let animal: Dog = Dog() 하면 Static Dispatch?

  • 아니다! Dog 타입으로 선언해도, final 아니면 Dynamic Dispatch
  • 이유: Dog가 상속 가능 → 서브클래스가 메서드를 오버라이드할 수 있음
  • 컴파일러는 안전을 위해 vtable 사용
  • Static Dispatch를 원하면: final class Dog 선언 필수 ✅
  • final = 상속 불가 = 오버라이드 불가 = 컴파일 타임 확정

🆕 Private 메서드는 항상 Dynamic Dispatch?

  • 아니다! 컴파일러가 Static Dispatch로 최적화 가능
  • private = 이 클래스 안에서만 접근 가능
  • 서브클래스에서 오버라이드 불가 → vtable 불필요
  • 컴파일러가 "항상 이 구현만 호출된다"고 확신 가능
  • → WMO(Whole Module Optimization) 활성화 시 더 적극적 최적화

🆕 Inline 저장되면 Static Dispatch?

  • 아니다! Inline 저장 ≠ Static Dispatch (완전히 별개 개념)
  • Inline 저장: 메모리 할당 방식 → 캐시 효율성 (메모리 성능)
  • Static Dispatch: 메서드 호출 방식 → 컴파일 타임 결정 (호출 성능)
  • any Protocol은 값이 inline 저장되어도, 메서드 호출은 Dynamic Dispatch
  • let drawable: any Drawable = Point() → Point는 inline 저장 (메모리 빠름), 하지만 drawable.draw()는 PWT 조회 (메서드 느림)

🆕 Inline의 정확한 정의 (메모리 점프)

  • Inline 저장: Value Buffer에 데이터를 직접 저장 (연속된 메모리 주소) ✅
  • Container 읽을 때 메모리 점프 없이 바로 데이터 접근 가능
  • Non-Inline 저장: Value Buffer에는 힙 포인터만 저장 ❌
  • 실제 데이터는 힙의 다른 주소 → 메모리 점프 발생 (Container → 힙)
  • → 메모리 점프 = 캐시 미스 가능성 → 10-30배 느림

🔬 "인라인"은 두 가지 완전히 다른 의미!

  • A. Inline 저장 (메모리): 연속 메모리에 데이터 직접 저장 → 캐시 효율성
  • B. 함수 인라이닝 (최적화): 함수 호출 제거하고 코드 직접 삽입 → 호출 오버헤드 제거
  • "Dynamic Dispatch는 인라이닝 불가" ← 여기서 인라이닝 = 함수 인라이닝!
  • → 두 개념은 완전히 별개! 문맥에 따라 의미가 다름

🔬 vtable은 왜 데이터 세그먼트에?

  • vtable = 함수 주소 배열 (예: [0x3000, 0x2000])
  • vtable 자체는 "데이터"이지 "코드"가 아님
  • 코드 세그먼트 = 실행 가능한 기계어 코드 (Dog.sound() 코드)
  • 데이터 세그먼트 = 전역 변수, 상수, vtable (주소 배열)
  • → vtable은 주소 값들을 담은 배열이므로 데이터 세그먼트에 저장

🔬 Struct는 메서드 주소를 가지지 않는다!

  • Struct 인스턴스 = 데이터만 (메서드 주소 없음)
  • dog.bark() → 컴파일러가 call 0x3000으로 변환 (주소 하드코딩)
  • 0x3000 = 코드 세그먼트 주소 (Dog.bark() 함수 코드)
  • Class는 vtable pointer를 가지지만, Struct는 아무 포인터도 없음
  • → Struct 메서드 = 실제로는 전역 함수처럼 동작

🔬 "포함하는 객체에 직접 저장"의 의미

  • Struct 프로퍼티: 포함하는 객체 안에 직접 저장 (포인터 없음, 연속 메모리)
  • 예: class VM { var point: Point } → Point가 VM 안에 직접 포함
  • Class 프로퍼티: 포인터로 참조 (별도 힙 할당, 메모리 점프)
  • 예: class VM { var dog: Dog } → Dog는 별도 힙에 할당, 포인터만 저장
  • → Struct = 값 복사 (연속 메모리), Class = 참조 복사 (포인터)

🔄 다음에 할 일

  • 카카오페이 블로그 "문제 해결을 위해 우리가 한 일" 섹션 마저 읽기
  • Swift Concurrency race condition 실습 프로젝트 분석

2026-06-12 (목)

✅ 학습 완료

📚 읽은 자료

📕 오답노트

struct는 isolated가 아니야?

  • 아니다! struct도 non-isolated (serial executor 없음)
  • 하지만 값 타입이라 복사되므로 동시성 안전
  • isolated = serial executor 있음 (Actor, @MainActor만)
  • non-isolated = serial executor 없음 (struct, class, Task 등)

await Task { }는 Task 생성만 기다리는 거 아냐?

  • 아니다! await Task { }.value는 Task 완료까지 기다림
  • await = 비동기 작업이 완료될 때까지 현재 실행 일시 중단
  • await으로 순차 실행하면 class여도 안전

Task 1, 2, 3 실행 순서가 보장돼?

  • 아니다! 생성 순서는 보장되지만 실행 순서는 보장 안 됨
  • Actor의 serial executor가 순차 처리하지만, 어떤 Task가 먼저 들어올지는 스케줄러가 결정
  • 순서 보장 필요하면 await으로 완료 대기

🔄 다음에 할 일

  • Swift Concurrency Actor 심화 학습
  • Sendable 프로토콜 정리

2026-06-13 (토)

✅ 학습 완료

📖 읽은 자료

📕 오답노트

"메인에서 만든 Task니까 안은 전부 메인" — 맞나?

  • 틀림. Task { }는 MainActor를 상속해 메인에서 시작하지만, 안에서 nonisolated asyncawait하면 그 함수 본문은 글로벌 풀에서 실행됨
  • 리턴하면 다시 MainActor로 복귀 → 다음 줄은 메인
  • 전제: UIViewController=@MainActor, Swift 5.7+(SE-0338)

🔄 다음에 할 일

  • 카카오페이 블로그 "문제 해결을 위해 우리가 한 일" 섹션 이어 읽기
  • Actor reentrancy 심화 / Actor vs Lock

2026-06-14 (일)

✅ 학습 완료

📕 오답노트

viewDidLayoutSubviews가 frame을 "적용"하는가?

  • 아니다. frame을 실제 세팅하는 건 layoutSubviews(뷰 레벨, top-down)
  • viewDidLayoutSubviews는 VC 루트뷰 레이아웃이 끝난 뒤의 알림 콜백
  • 제약 변경은 setNeedsLayout() 직접 안 불러도 자동 dirty

🔄 다음에 할 일

  • 뷰 레이아웃 2단계 — intrinsic content size + CHCR(Hugging/Compression Resistance)