SwiftUI Type System

SwiftUI에서 some View를 쓰는 이유

"왜 굳이 some을 쓰는가"를 문법 설명이 아니라 타입 시스템 관점에서 정리한다. 핵심은 표기는 숨기고 컴파일러 타입 정보는 유지다.

학습 날짜

2026-04-30

1. 결론 먼저

  1. some View는 구체 타입 표기를 감춘다.
  2. 하지만 컴파일러는 실제 구체 타입을 정확히 안다.
  3. 그래서 API는 단순하고, 타입 안정성과 최적화 여지는 유지된다.

2. Self.Body를 쓰는 이유

public protocol View {
    associatedtype Body : View
    @ViewBuilder @MainActor @preconcurrency var body: Self.Body { get }
}

View는 associatedtype 기반이라 body는 "어떤 View든"이 아니라 "이 타입이 정한 구체 Body"여야 한다.

3. 왜 그냥 View가 아닌가

var body: View로 쓰면 existential(타입 소거) 의미가 강해진다. SwiftUI는 뷰 트리의 구체 타입 정보를 활용해 diff/최적화를 한다.

4. @ViewBuilder가 "합성 타입" 만든다는 뜻

@ViewBuilder
var content: some View {
    if isLoading {
        ProgressView()
    } else {
        Text("Done")
    }
}

분기마다 반환 뷰 타입이 달라도, 빌더가 내부적으로 단일 합성 타입(ConditionalContent 등)으로 묶어줘서 some View 규칙을 만족시킨다.

5. any View / AnyView와 비교

항목some ViewAnyView / any View
타입 정보컴파일 타임 유지소거됨
주 용도기본 body 반환동적 슬롯/이종 컬렉션
비용낮은 편추가 추상화 비용 가능

6. 실무 규칙

  1. 기본은 some View.
  2. 분기/조합은 @ViewBuilder로 해결.
  3. 정말 동적 타입 슬롯이 필요할 때만 AnyView 검토.

7. 최근 질문 Q&A

질문핵심 답
"컴파일에는 알리고 표기상 숨긴다"는 뜻?맞다. 외부 API 표면은 단순화하고, 컴파일러 타입 정보는 유지한다.
some은 쓸일이 없지 않나?앱 내부 일반 코드에선 적을 수 있지만 SwiftUI body/API 반환에서는 핵심 패턴이다.
var body: some View여야 하는 이유?View 프로토콜의 associatedtype 요구를 만족하면서도 구체 타입 노출을 피하기 위해서다.

8. 2026-07-16 Q&A — "구체타입을 몰라도 쓰게 하는 것 맞아?"

주인님 이해: "opaque 타입은 선언부에는 구체타입을 알고있으나, 호출부에서는 프로토콜 타입만 알게 해서 구체타입을 몰라도 사용할 수 있게 하는 것 맞아?"

결론: 절반은 정확, 한 군데가 결정적으로 부정확. "호출부는 프로토콜 타입만 안다"는 표현은 some View가 아니라 any View(existential)를 설명하는 말이다.

핵심 정정
some View는 구체 타입이 하나로 고정되어 있고 컴파일러는 그게 뭔지 정확히 안다. 단지 프로그래머(호출부 소스코드)가 그 이름을 못 볼 뿐. "타입을 지우는" 게 아니라 "이름만 가리는" 것이다.

정확히 고친 문장: opaque 타입은 구현부(함수 몸통)가 구체 타입을 하나로 확정하고, 컴파일러는 그걸 알지만 호출부 코드에서는 그 타입 이름을 숨긴다. 호출부는 "이건 View다"까지만 쓸 수 있다.

some View vs any View — 4가지 차이

some View (opaque)any View (existential)
구체 타입하나로 고정 (컴파일 타임)여러 개 가능, 런타임에 바뀜
컴파일러가 아는가안다모른다 (박스로 감쌈)
타입 정체성(identity)보존됨사라짐 (type-erased)
디스패치/성능정적 디스패치, 오버헤드 X동적 디스패치 + 박싱 오버헤드

opaque = "역방향 제네릭"

// ✅ OK — return 경로가 하나의 구체 타입(Text)만 반환
func makeView() -> some View { Text("hi") }

// ❌ 컴파일 에러 — some은 "하나의 타입"이어야 하는데 둘이 다름
func makeView(flag: Bool) -> some View {
    if flag { Text("hi") }   // Text
    else    { Image("x") }   // Image → 타입이 갈림!
}

// ✅ any면 됨 — 실존 타입은 서로 다른 타입을 담을 수 있으니까
func makeView(flag: Bool) -> any View {
    if flag { Text("hi") } else { Image("x") }
}

SwiftUI가 any가 아니라 some을 쓰는 진짜 이유

body의 실제 타입은 VStack<TupleView<(Text, Button<Text>)>> 처럼 끔찍하다.

  1. 적기 싫다some View로 숨긴다 (편의성).
  2. 왜 any가 아닌가? → SwiftUI의 diffing이 View의 구체 타입 정체성으로 "같은 뷰인가/새 뷰인가"를 판단한다. any View면 identity가 사라져 diffing이 깨지고 매 프레임 박싱 비용도 든다. opaque는 "컴파일러는 알되 코드만 숨김"이라 이 요구를 완벽히 만족한다.

한 줄 요약: opaque는 타입을 "지우는" 게 아니라 "이름만 가리는" 것. 컴파일러는 여전히 하나의 구체 타입을 안다 — 그래서 성능과 identity가 살아있다. "프로토콜 타입만 알게 된다"는 any View 쪽 설명이다.

9. 2026-07-16 후속 Q&A — 방향/정체성/컴파일러/diffing/박싱

Q1. opaque는 구현부가 타입을 알고 호출부는 모른다, 제네릭은 반대 맞지?

맞다. 제네릭은 입력을 추상화(호출부가 T를 채움), opaque는 출력을 추상화(구현부가 타입을 채움). 그래서 opaque = 역방향 제네릭(reverse generic).

타입을 결정/아는 쪽모르는 쪽
제네릭 f<T>(_:T)호출부 (f(3)→T=Int)구현부 (제약된 것만 사용 가능)
opaque f()->some P구현부 (return Text())호출부 (이름 모름)

Q2. 타입 정체성(type identity)이란?

"컴파일러가 두 값이 정확히 같은 구체 타입임을 보증하는가." some View는 하나의 구체 타입으로 고정되므로, 같은 함수가 반환한 두 값이 "같은 타입"임을 컴파일러가 안다(배열에 함께 담기, == 비교 등 성립). any View는 Text일 수도 Image일 수도 있어 이 보증이 사라짐(type-erased).

⚠️ 함정: 이 type identity(타입 시스템 개념)와 Q4의 SwiftUI View identity(뷰가 "같은 존재"인가)는 이름만 비슷한 다른 개념이다.

Q3. "컴파일러가 아는가"에서 뭘 안다는 것?

구체 타입 그 자체 전부 — Text인지 VStack<...>인지, 그 타입의 메모리 크기·레이아웃, 어느 메서드의 어느 구현을 호출할지까지. 그래서 정적 디스패치 · 인라이닝 · 스택 할당 · 스페셜라이즈가 가능. any View면 "View를 따른다"만 알아 매번 witness table 경유 → 동적 디스패치.

Q4. "diffing이 View의 구체 타입을 쓴다"는 뜻

SwiftUI는 View 값(struct) 트리를 들고 있다가, 상태가 바뀌면 body를 다시 실행해 새 트리를 만들고 이전 트리와 diff한다. 각 노드의 구체 타입 + 트리 위치가 그 뷰의 정체성(structural identity):

이 판단에 구체 타입이 필요한데 some View라 타입이 정적으로 고정돼 가능. 전부 AnyView로 감싸면 노드 타입이 AnyView로 뭉개져 정체성·애니메이션·상태 보존 최적화가 어려워진다(= AnyView 남발이 성능/버그 원인).

Q5. "프레임 박싱" = existential box 생성 맞아? (value 24byte + VWT + PWT)

맞다. any View/AnyView를 만들 때 existential container(박스)를 생성한다. 64-bit 레이아웃:

┌─────────────────────────────┐
│ inline value buffer  3 words │ = 24 byte
│  값 ≤24byte → 여기 직접 저장     │
│  값 >24byte → 힙에 얹고 포인터만  │
├─────────────────────────────┤
│ metadata pointer  1 word     │ → VWT(Value Witness Table) 도달
│                              │    값의 copy/move/destroy = 메모리관리
├─────────────────────────────┤
│ PWT pointer(s)  프로토콜당 1개  │ → Protocol Witness Table
│                              │    프로토콜 메서드 → 구체 구현, 동적 디스패치
└─────────────────────────────┘

any View 비용 = ① 박스 생성(크면 힙 할당) + ② witness table 간접 디스패치 + ③ diffing 최적화 방해. some View는 셋 다 없음.

10. 2026-07-16 심화 Q&A — 프로토콜=타입?/정적디스패치/body재실행/identity/AnyView

Q1. 같은 프로토콜을 따르면 같은 타입 아닌가?

아니다. 프로토콜 준수 ≠ 같은 타입. Text와 Image는 둘 다 View를 따르지만 완전히 다른 타입. "같은 타입"은 구체 타입이 물리적으로 동일(Text==Text)한 것. 프로토콜 준수는 "같은 계약을 만족"할 뿐.

비유: 사람과 개가 둘 다 동물을 따라도 사람≠개. any Animal은 "동물이다"까지만 보장. → 그래서 any View는 type identity를 잃고(=구체 타입 보장 못 함), some View는 "정확히 그 하나의 타입"을 보장한다.

Q2. 컴파일타임에 함수 주소를 알아 witness table 조회 불필요?

맞다. 구체 타입을 아니까 정적 디스패치(주소 컴파일 타임 확정, PWT 조회 없음) + 인라이닝까지 가능.

디스패치조회 방식
정적 (some·구체타입·final·값타입 메서드)컴파일 타임 주소 확정, 조회 없음
동적 — 클래스vtable 조회
동적 — 프로토콜 존재타입(any)witness table(PWT) 조회

Q3. body 재실행 시 바뀐 struct만? 전부 다시 만드나?

두 층으로 나눠야 정확하다:

상태 변경
 → 그 상태를 읽는 뷰들의 body만 재실행 (dependency 추적)
   → 그 서브트리 자식 struct는 전부 새로 생성 (값이라 쌈)
     → 이전 struct 트리와 diff
       → 실제로 바뀐 백킹 객체만 갱신 (렌더 통째 재생성 X)

Q4. 같은 위치·같은 타입인데 내부 값만 바뀌면 다른 뷰야?

아니다. 같은 뷰(같은 identity)다. 정체성은 타입 + 위치로 정해지지 속성값으로 정해지지 않는다.

Text("A") → Text("B")
// 같은 위치, 같은 타입(Text) → "같은 Text 뷰"
// 내용만 A→B로 diff해서 in-place 업데이트
// 이 뷰 안의 @State는 그대로 유지됨 ✅

이게 의도된 동작: TextField 텍스트가 바뀌어도 "같은 뷰"라 커서·포커스 유지, 애니메이션도 이어짐. 내부 값 바뀔 때마다 "다른 뷰"로 취급하면 매번 파괴·재생성돼 상태가 다 날아감.

반대로 강제로 다른 뷰로 만들려면 .id(value). id가 바뀌면 타입이 같아도 "다른 뷰"로 취급 → 파괴+재생성+상태 리셋 (explicit identity).

Q5. 왜 AnyView면 타입 바뀐 걸 구분하기 흐려지나?

AnyView로 감싸면 정적 타입이 전부 AnyView 하나로 뭉개지기 때문. SwiftUI의 structural identity는 노드의 정적(컴파일 타임) 타입으로 판단하는데:

// some View 세계 — 정적 타입에 구조가 다 드러남
VStack<TupleView<(Text, Image)>>   // "여기 Text, 여기 Image" 다 앎

// AnyView 세계 — 전부 AnyView로 붕괴
AnyView(Text(...))   // 정적 타입: AnyView
AnyView(Image(...))  // 정적 타입: AnyView  ← 정적으로는 구별 불가

AnyView(Text)AnyView(Image)로 바뀌어도 정적 타입은 둘 다 AnyView라, 컴파일 타임 타입만으론 "내용물 타입이 바뀌었다"를 알 수 없다. 알려면 런타임에 박스를 열어 dynamic type 비교 필요.

한 줄: some은 타입에 트리 구조가 통째로 새겨져 SwiftUI가 컴파일 타임에 정체성 판단 / AnyView는 그 구조를 AnyView 하나로 지워 정적으로 볼 게 없어짐.

11. 2026-07-17 심화 Q&A — identity vs type vs struct / 백킹스토어 / 상태 끌어올리기

이 섹션의 대전제 (제일 헷갈리는 지점): identity(정체성)는 "값"이 바뀐다고 바뀌지 않는다. "위치(구조적 자리)"로 정해진다. 저장 프로퍼티·@State 값이 바뀌어도 identity는 그대로다(그래서 상태가 살아남는다). identity가 바뀌는 건 오직 ① 위치 이동(if/else 분기 갈아탐·다른 컨테이너) ② .id() 변경 ③ ForEach 요소 id 변경일 때뿐.

Q1. property wrapper의 getter가 "훅킹"돼서 읽기로 기록된다? 훅킹이 뭐야?

훅킹(hooking) = 어떤 동작이 일어나는 지점에 끼어들어 추가 코드를 함께 실행하는 것. count를 읽으면 실제로는 _count(=State<Int>)의 wrappedValue getter가 호출되는데, SwiftUI가 만든 그 getter 안에는 값 반환 외에 "지금 body 평가 중인 이 뷰가 나(count)에 의존한다"고 그래프에 등록하는 코드가 얹혀 있다. 즉 접근이 곧 함수 호출이라(property wrapper라서) 그 함수에 의존성 기록 로직을 걸어둔 것. 메서드 스위즐링이 아니라 래퍼 getter에 로직이 있는 것.

Q2. identity와 type은 다른 개념인가?

완전히 다르다.

if isOn { CounterView() }   // if-분기 슬롯 → identity A
else    { CounterView() }   // else-분기 슬롯 → identity B
// 타입은 같은 CounterView, identity는 A/B로 다름
// → 토글하면 "다른 뷰"로 취급돼 @State 리셋

여기서 "위치"는 화면 좌표가 아니라 소스 코드의 구조적 자리(어느 분기냐). 런타임에 화면엔 하나만 보여도 SwiftUI는 두 분기를 별개의 identity 슬롯 2개로 취급한다. 그래서 결과 화면이 같아도 identity가 다르다.

Q3. identity 노드가 붙드는 게 @State뿐? Binding/Observable/Environment는?

소유(owns) vs 참조(references)를 구분해야 한다.

구분대상identity 노드와의 관계
원본 소유@State, @StateObject, @GestureState노드가 직접 저장(소유) — source of truth
참조만@Binding남의 @State를 가리키는 포인터(자기 저장소 없음)
참조만@ObservedObject관찰만, 생명주기 소유 X(부모가 다시 만들면 갈아탐)
참조만@Environment환경에서 읽기

그래서 노드가 사라져도 원본이 상위에 있으면 참조 대상은 안 죽는다(Q9의 핵심).

Q4. "identity가 바뀐다"를 구체적으로

SwiftUI는 각 뷰 슬롯에 보이지 않는 식별 토큰을 붙인다. 업데이트 때 그 자리의 토큰이 그대로면 → 같은 뷰 → 노드·@State 유지, 내용만 in-place 갱신. 토큰이 달라지면(분기 갈아탐/.id()/ForEach id 변경) → "여긴 이제 다른 뷰다" → 옛 노드 파괴(@State 폐기) → 새 노드 생성(초기값부터). 값 변화로는 안 일어나고 자리/식별자 변화로만 일어난다.

Q5. struct 재생성해도 identity 트리는 그래프 스토리지에 남나? / 노드 == 트리?

Q6. "State 래퍼"가 State를 감쌌다는 뜻?

@State var count: Int이면 _count타입이 State<Int>이고, 이 State<Int> 자체가 property wrapper(래퍼)다. 이 래퍼가 Int 값과 백킹 스토어 핸들을 감싸 관리한다. @State_count = State(wrappedValue: 0)을 만들어주는 문법 설탕. (백킹 스토어는 하나 — 이전 답의 "2번(항목 번호)의 백킹 스토어"를 "두 번"으로 오해했던 부분 교정)

Q7. 그래프 스토리지가 일반 저장 프로퍼티(var k=0)도 갖고 있나?

아니다. 일반 저장 프로퍼티는 그냥 struct 값의 일부라 struct와 함께 매번 재생성·폐기된다. 그래프 스토리지에 지속 저장되는 건 @State·@StateObject 같은 상태 래퍼뿐. 일반 프로퍼티 값은 부모가 매번 넘겨주는 입력(스스로 기억 안 함). 단 SwiftUI는 직전 struct 값을 diff 비교용으로만 잠깐 들고 있다(상태 보존이 아니라 비교 스냅샷).

Q8. 일반 프로퍼티(var k=0)가 바뀌어도 body 재호출되나?

재호출된다. body 재호출 기준은 "입력(저장 프로퍼티) 값이 직전과 달라졌나"지 "@State냐 일반이냐"가 아니다. k가 이전과 다르면 diff에서 "달라짐" → body 재실행. 같으면 스킵. 차이는 변화를 누가 일으키냐뿐 — 일반 프로퍼티는 부모가 새 값을 넘겨야 바뀌고(자기 스스로 못 바꿈), @State는 자기 안에서 바뀔 수 있다.

Q9. ProgressView↔Text 토글 — struct·identity·자식 identity 다 새로?

전부 새로 생긴다. struct 재생성(항상) + 슬롯 타입이 ProgressView→Text로 바뀜 → 다른 identity → 옛 ProgressView 노드 파괴 + 하위 노드도 같이 폐기 → 새 Text 노드와 하위 노드가 fresh identity로 생성. 서브트리 통째 교체라 상태·애니메이션이 끊긴다("무거운" 변화).

Q10. 상태 끌어올리기(state lifting) — "identity 노드 두 개가 같다"인가?

struct Parent: View {
    @State private var count = 0            // Parent 노드가 소유
    var body: some View {
        if isOn { ChildView(count: $count) }   // 자식은 @Binding(참조)만
        else    { OtherView(count: $count) }
    }
}

"ChildView와 OtherView의 identity 노드가 같다"는 틀리다. 둘은 다른 타입 + 다른 분기라 서로 다른 identity 노드이고, 토글마다 파괴·생성된다. 그런데도 count가 안 날아가는 이유는:

한 줄: count 보존은 자식이 노드를 공유해서가 아니라, 원본을 안정적인 상위(Parent) 노드가 들고 있어서다. 자식이 각자 @State를 가졌다면 그건 토글마다 리셋된다. 이게 "상태 끌어올리기"의 원리 — 상태 소유권을 identity가 튼튼한 상위 뷰로 이동.

번외. 정적 디스패치 — class의 private func / private let도?

12. 2026-07-17 후속 심화 Q&A — ForEach id / State 박스 재연결 / body 재호출 트리거 / state lifting 오해 교정

Q1. ForEach view는 자리가 바뀌어도 같은 identity(=State) 유지?

안정적·고유한 id를 쓰면 유지된다. 항목이 자리를 옮겨도 SwiftUI가 id로 매칭해 같은 노드를 새 위치로 이동 → @State·스크롤·애니메이션이 항목을 따라간다. 반대로 인덱스를 id로 쓰면 자리를 바꿨을 때 상태가 "자리"에 눌러앉아 엉뚱한 행에 붙는 버그. → 실무: 서버 UUID 같은 고유·안정 id를 써라.

Q2. "애니메이션 리셋"이 뭐야?

SwiftUI 애니메이션은 "같은 identity 뷰가 값 A→B로 변했다"를 보고 A↔B를 부드럽게 보간한다. identity가 바뀌면(노드 파괴·재생성) 보간할 '이전 값'이 없어져 진행 중이던 애니메이션이 뚝 끊기고 새 뷰가 초기 상태에서 튀어나온다. = identity 변경으로 보간 기준점이 사라져 애니메이션이 이어지지 못하는 것.

Q3. "모디파이어가 새 래퍼 타입을 만든다"?

Text("hi")                 // Text
Text("hi").padding()       // ModifiedContent<Text, _PaddingLayout>
Text("hi").padding().opacity(0.5)
// ModifiedContent<ModifiedContent<Text, _PaddingLayout>, _OpacityEffect>

모디파이어마다 ModifiedContent<...> 래퍼 타입으로 감싼다. 하지만 SwiftUI는 이걸 같은 뷰의 "치장"으로 봐 identity에 영향 없음(패딩 넣었다고 @State 리셋 X). body 타입이 이렇게 길어지니 some View로 가리는 것.

Q4. VStack은 노드이자 트리?

맞다. VStack은 노드 하나이면서, 아래 자식이 매달리므로 서브트리의 뿌리. 노드는 트리의 한 점이고, 자식을 가지면 그 점이 서브트리의 root가 된다. "전체 트리 안에 VStack 노드가 있고 그 아래로 서브트리가 뻗는다"가 맞음.

Q5. "서브트리만 재생성" — 일반 프로퍼티도 마찬가지?

마찬가지다. 단 트리거(출발점)가 다르다. @State는 트리거가 그 뷰 자신. 일반 프로퍼티는 스스로 못 바뀌고, 값이 달라지는 유일한 경로는 상위 @State가 바뀌어 상위 body가 다시 돌며 새 값을 주입하는 것. 즉 일반 프로퍼티 변화는 항상 상위 상태 변화의 하류라, 재생성 범위도 그 상위 뷰부터 아래로. 전체 앱 트리는 절대 아님.

Q6. "그 변수를 사용할 때만 재생성" — 이 이해 맞아? (두 메커니즘 구분)

변화 대상재호출 기준"사용할 때만"?
@State(의존성 추적)지난 body에서 읽은(read) 뷰만 무효화✅ 맞음
부모 주입 일반 프로퍼티새 struct vs 옛 struct 필드 비교 → 값 다르면 재호출❌ 사용 안 해도 재호출

즉 "@State는 사용할 때만"은 맞고, "일반 변수도 사용할 때만"은 부정확. 부모 주입 프로퍼티는 값이 다르면 body에서 안 써도 재호출(.equatable()로만 최적화).

Q7. projectedValue / "body 평가중" / dirty·getter / _count / =0

Q8. @State 아닌 일반 프로퍼티는 애초에 "변경"이 불가능

View는 불변 값 타입. @State/@Binding 없는 일반 프로퍼티는 init에서 정해지면 런타임에 못 바꾼다.

struct MyView: View {
    var k = 0
    var body: some View {
        Button("tap") { k += 1 }   // ❌ 컴파일 에러: self is immutable
    }
}

"일반 프로퍼티 값 변경" 자체가 스스로는 불가능. 값이 달라지는 유일한 길은 부모의 재주입. 바꾸려면 @State여야 하고, 그러면 dirty→재호출로 이어진다.

Q9. ⭐ state lifting 오해 교정 — "if/else면 바인딩 받은 상태도 사라지지 않아?"

핵심: 상태는 "선언한 뷰의 노드"에 저장된다. 누가 소유하느냐가 전부.

// 경우 A — 자식이 소유 → 토글 시 자식 노드째 삭제 → 증발
struct ChildView: View { @State private var count = 0 }
if isOn { ChildView() } else { OtherView() }

// 경우 B — 부모가 소유, 자식은 @Binding 참조
struct Parent: View {
    @State private var count = 0                 // count는 Parent 노드에 저장 ★
    var body: some View {
        if isOn { ChildView(count: $count) }     // 자식은 포인터만
        else    { OtherView(count: $count) }
    }
}

경우 B에서도 ChildView 노드는 토글 시 파괴된다(맞음). 하지만 count는 ChildView가 아니라 Parent 노드에 산다. 그리고 Parent는 if/else를 담는 그릇이지 그 안에 있는 게 아니라, 토글에도 Parent identity는 그대로 → count 생존 → 새 OtherView가 같은 Parent count에 다시 바인딩 → 값 유지.

오해 교정: "바인딩으로 받은 상태도 if/else면 사라진다" → 아니다. 그 상태는 자식이 아니라 부모에 저장돼 있고 부모는 안 죽는다. 자식이 죽어도 자식은 소유자가 아니라 참조자(포인터)일 뿐 → 원본은 멀쩡. 죽는 건 포인터뿐, 다음 자식이 같은 원본으로 새 포인터를 만든다.
경우 A: [ChildView 노드 { count=5 }]         ← 토글 시 노드째 삭제 → 증발
경우 B: [Parent 노드 { count=5 }]            ← 토글에도 생존 → 유지
           └ [ChildView 노드 { $count →↑ }]  ← 이 노드만 삭제(포인터만 사라짐)

state lifting = 상태를 "잘 죽는 자식"에서 꺼내 "안 죽는 부모"로 이사시키는 것. 자식이 죽어도 상태가 안 죽는 이유는 상태가 더 이상 자식 안에 없기 때문.

번외. State<Int>의 "박스 재연결" — struct 재생성돼도 값이 사는 이유

State<Int>는 Int를 직접 담지 않고 SwiftUI 관리 저장소의 "박스"를 가리키는 핸들만 든 작은 struct. View struct가 매 업데이트 재생성돼 _count도 새 struct가 되지만, 박스 참조를 채우는 건 초기화자(=0)가 아니라 SwiftUI — body 실행 직전에 identity 노드의 기존 박스를 새 State에 다시 연결(re-inject). =0은 박스 최초 생성 때만 쓰이고 이후 무시. (리모컨은 매번 새로 쥐어줘도 TV(박스)는 노드에 그대로 → 채널 유지)

13. 2026-07-18 정리·정정 — 등록 타이밍 / body↔struct 인과 / 일반 프로퍼티 함정

Q1. "상태가 항목을 따라감"(ForEach)

안정적 id를 쓰면 각 행의 @State(펼침/토글 등)가 화면 자리가 아니라 항목(id)에 붙어, 목록을 정렬해 항목이 이동해도 그 상태가 항목을 쫓아간다. 인덱스 id면 상태가 "자리"에 눌러앉아 엉뚱한 항목이 펼쳐진 채 남는다.

Q2. "@State가 리셋 안 됨"이 왜 중요/왜 자주?

SwiftUI가 struct를 초당 수십·수백 번 재생성해도 앱이 도는 건 @State가 그 재생성을 건너 살아남기 때문. 깨지면 즉시 버그: TextField 입력·커서 증발 / 스크롤 리셋 / 토글 원위치 / @StateObject 재생성→네트워크 재호출·데이터 소실 / 애니메이션 끊김. 원인은 거의 항상 identity 불안정(ForEach id·if/else 안 뷰·AnyView·.id() 오용) → SwiftUI 디버깅의 핵심.

Q3. ModifiedContent도 View?

그렇다. ModifiedContent<Content, Modifier>는 View 준수. Text().padding()도 여전히 View라 모디파이어 체이닝이 "View 세계"에 머문다.

Q4. body는 "그림"이 아니라 "설계도" / body↔struct 인과

Q5. 의존성을 "어디에" 등록/저장?

struct 안이 아니라 SwiftUI 내부 의존성 그래프(attribute graph). 각 @State 박스에 "나를 읽은 뷰들의 명단"이 딸리고, body 평가 중 읽으면 "이 뷰→이 값" 간선이 그려진다. 값이 바뀌면 그 명단을 무효화→body 재평가 예약. 뷰 struct는 명단을 안 가짐(그래프가 가짐).

Q6. ⭐ "count 등록"은 언제? (핵심)

body 평가 중 getter가 호출되는 순간 등록된다. identity 생성 때(X)도, 쓰기 때(X — 쓰기는 무효화 트리거)도 아니다. 그리고 등록은 매 body 평가마다 지웠다 다시 그린다 → "이번 body에서 실제로 읽은 것"만 정확히 구독.

[body 평가] count 읽음(getter) → "이 뷰→count" 간선 등록
count에 write(setter) → 등록된 뷰들 무효화 → body 재평가
  → 재평가하며 다시 getter → 간선 다시 등록(매번 갱신)

getter=명단 작성(매 body 갱신), setter=명단의 뷰들 재실행 트리거.

Q7. body 재호출 과정 순서

1. 부모 body 실행 → 자식 struct를 새 값(주입 프로퍼티 포함)으로 생성
2. 비교: 부모가 준 필드가 이전과 다른가? / 자식 자신 @State가 dirty인가?
3. 다르면 → 자식 body 호출 → 손자 struct 생성 → 재귀
   같으면 → 자식 body 스킵

즉 ① struct 생성 → ② 필드/상태 diff → ③ 달라졌으면 body 호출. (자신 @State 변화는 부모 없이도 직접 dirty로 이 흐름에 합류)

Q8. projectedValue = 박스를 가리키는 값

@State의 projectedValue(=$count)는 그래프 스토리지의 같은 박스를 가리키는 Binding<Int>. 자식에게 $count를 넘기면 자식이 그 박스를 직접 읽고 쓴다.

Q9. 일반 프로퍼티와 SwiftUI — 바꿀 수 있는 경우? (참조타입 함정)

SwiftUI가 추적하는 상태는 property wrapper(@State·@Binding·@StateObject·@ObservedObject·@Environment)뿐. 일반 프로퍼티는 "부모가 주는 불변 입력". 변경 경로:

한 줄: 일반 프로퍼티 = SwiftUI에겐 "읽기 전용 입력". 변경으로 UI를 움직이려면 반드시 상태 래퍼를 거쳐야 한다.

14. 2026-07-18 Q&A — 인덱스 id 함정 / diff 순서 / 자기 @State와 struct / 바인딩 전달 ≠ 읽기

Q1. "인덱스를 id로 쓴다" / "ForEach id 잘못"이 뭐야?

// ❌ 인덱스를 id로 — 위험
ForEach(0..<items.count, id: \.self) { i in RowView(items[i]) }
ForEach(Array(items.enumerated()), id: \.offset) { ... }
// ❌ 값 자체를 id로 — 값 바뀌거나 중복이면 위험
ForEach(items, id: \.self) { ... }   // items가 String 등 값타입일 때
// ✅ 안정적 고유 id
struct Item: Identifiable { let id = UUID() }
ForEach(items) { item in RowView(item) }   // 자동 id: \.id

항목을 삽입/삭제/정렬하면 같은 항목이 다른 인덱스를, 다른 항목이 같은 인덱스를 갖게 된다. SwiftUI는 id로 매칭하니 "id=1 자리"의 @State가 새로 그 자리에 온 다른 항목에 달라붙는다(펼침·입력값이 엉뚱한 행에). "ForEach id 잘못" = 불안정한 id(인덱스·바뀌는 값·중복) 지정 → @State 리셋 버그의 흔한 원인. 해결: Identifiable + 안정적 UUID.

구체 예시 — 각 행이 "펼침"을 자기 @State로 가짐. B를 펼친 상태(id=1)에서 맨 위 A를 삭제하면:

초기:  id=0→A(접힘)  id=1→B(펼침)★  id=2→C(접힘)
A 삭제 후 (인덱스 id): B는 index0→id=0, C는 index1→id=1
SwiftUI가 id로 매칭 → @State는 "자리"에 그대로 남음:
  id=0: A→B 로 바뀜, 그 자리 @State=접힘 유지 → B가 접혀버림(펼침 소실!)
  id=1: B→C 로 바뀜, 그 자리 @State=펼침 유지 → C가 펼쳐짐(B 상태 뺏음!)

= @State가 항목(B)이 아니라 자리(id=1)에 눌러앉아 항목 이동 시 엉뚱한 곳에 붙음. UUID id면 B의 id가 위치 무관 고정 → B UUID↔B UUID 매칭 → 펼침이 B를 따라 이동(정상). 인덱스 id="상태가 자리에", UUID id="상태가 항목에".

Q2. diff 비교 순서 — struct 재생성 다음에 주입/바인딩 비교?

부모 body 실행 → 자식 struct를 새로 construct(주입값·바인딩 포함)
  → 새 자식 struct vs 옛 자식 struct의 "저장 프로퍼티(입력)" 비교
       - 부모 주입 일반 프로퍼티 / @Binding·@ObservedObject 참조 등
  → 다르면 자식 body 재호출, 같으면 스킵

단 하나 구분: 자신의 @State 변화는 이 "비교"로 감지하는 게 아니다. @State는 "입력"이 아니라 "소유한 상태"라 비교 대상이 아니고, 바뀌면 비교 없이 곧장 그 뷰를 dirty로 표시(직접 무효화 경로). → 부모 주입/바인딩=struct 비교로 판단, 자신 @State=직접 무효화.

Q3. 자신의 @State가 변하면 struct도 재생성돼?

엄밀히는 아니다. 기존 struct를 유지한 채 body만 재실행한다. "struct 재construct"는 부모 body가 그 자식을 다시 만들 때 일어난다. 자기 @State만 바뀌면 부모는 안 도니 자기 struct는 새로 안 만들어지고, SwiftUI가 보관 중인 기존 뷰 값으로 body만 재실행(그 body가 자식 struct들은 새로 생성). 일반 저장 프로퍼티는 스스로 못 바뀌므로(불변 값타입) 이 경로에 애초에 안 낀다.

Q4. ⭐ 부모 @State를 자식이 바인딩만 하고 안 쓰면? (핵심 교정)

전제 교정: "부모 State가 변하면 부모 body가 돈다"가 아니다. @State가 바뀌면 body가 도는 건 그 값을 읽은(wrappedValue getter) 뷰뿐. 바인딩을 넘기는 것($count)은 projectedValue(포인터) 접근이지 "읽기"가 아니다 — 의존성 추적은 wrappedValue getter에만 걸린다.
struct Parent: View {
    @State private var count = 0
    var body: some View { ChildView(count: $count) }  // $count 전달만, count 값 안 읽음
}
struct ChildView: View {
    @Binding var count: Int
    var body: some View { Text("hi") }                // count 안 씀
}

count가 바뀌면: 부모—count 값 안 읽음→의존자 아님→body 안 돎→자식 재construct 안 함. 자식—count 안 씀→의존자 아님→body 안 돎. 결과: 아무도 body 재호출 안 하고 자식 struct 재생성도 없음.

"identity 노드의 그래프 스토리지 업데이트되지?" → 그건 된다. count의 박스(Parent 노드 저장소)에 새 값이 써지는 것 자체가 "count 변화"의 정의. 다만 그 박스를 읽는 뷰가 없어 화면 갱신은 없음. 자식은 count 저장소를 따로 안 가짐(바인딩=포인터)이라 Parent 박스 하나만 갱신(이중 업데이트 없음).

상황자식 struct 재생성자식 body 재호출
자식이 count 씀 + count 변함부모 돌면 예(의존자)
자식이 count 안 씀 + count 변함아니오아니오
부모가 다른 이유로 body 돎 + 입력 같음예(재construct)아니오(diff 같음 스킵*)
부모 body 돎 + 입력 다름

* 자식이 @Binding만 든 경우 바인딩 동등성 비교가 까다로워 SwiftUI가 보수적으로 body를 돌리기도 함(.equatable()로 통제).

한 줄: 부모 @State가 변한다고 부모가 도는 게 아니라 그 값을 실제로 읽은 뷰만 돈다. $count를 넘기기만 하고 아무도 count를 읽지 않으면 박스 값만 갱신되고 화면은 그대로.

Q5. 마지막 표 줄 상세 — "부모 body 돎 + 입력 다름 → 재construct·body 재호출" (해피 패스)

struct Parent: View {
    @State private var name = "Kim"
    var body: some View { ChildView(name: name) }   // 부모가 name을 "읽어서" 주입
}
struct ChildView: View {
    let name: String
    var body: some View { Text("Hello \(name)") }
}

name이 "Kim"→"Lee"일 때:

1. name 박스 값 변경("Kim"→"Lee")
2. name을 "읽은" 뷰 무효화 → Parent가 ChildView(name: name)에서 name 읽음 → Parent 무효화
3. Parent body 재실행 → 새 ChildView(name:"Lee") construct       ← 자식 재construct (예)
4. 입력 비교: 새 name "Lee" vs 옛 "Kim" → 다름
5. → ChildView body 재호출 → Text("Hello Lee")                   ← body 재호출 (예)
6. 렌더 층이 "Hello Kim"→"Hello Lee" 화면 갱신

세 번째 줄(입력 같음)과 대조: 부모가 다른 이유로 body 돌아도 자식에 주는 값이 그대로면 → 3번에서 재construct(예)하지만 4번 비교가 "같음" → 5번 body 스킵(아니오). struct 재생성은 둘 다 일어나지만(싸다), body 재호출은 입력이 달라졌을 때만. 마지막 줄=화면 갱신 정상 케이스, 세 번째 줄=값 안 바뀌어 body 건너뛰는 최적화 케이스.

15. 2026-07-18 — @State / @StateObject / @ObservedObject / @Observable 차이

두 축으로 갈린다:값 타입이냐 참조(class)냐 → @State(값) vs @StateObject/@ObservedObject(참조). ② 내가 소유(생성)하냐 관찰만 하냐 → @StateObject(소유) vs @ObservedObject(관찰). @Observable은 축이 아니라 관찰 메커니즘 자체를 바꾼 신형(iOS 17+).
대상 타입생명주기 소유?언제세대
@State값 타입 + @Observable 객체✅ 소유(1번 생성·유지)뷰 로컬 상태구·신 공통
@StateObject참조(ObservableObject)✅ 소유뷰가 직접 만드는 뷰모델구형
@ObservedObject참조(ObservableObject)❌ 관찰만밖에서 받은 객체구형
@Observableclass 매크로(소유는 @State가)뷰모델 정의신형(iOS 17+)

@State — 값 타입, 뷰가 소유

Int·String·Bool·struct·enum 등 값 타입. SwiftUI 관리 박스에 소유 → struct 재생성돼도 유지. 뷰 내부 로컬 상태(토글·입력·펼침)에 적합.

@StateObject — 참조 뷰모델을 뷰가 소유

ObservableObject class. 이 뷰가 만들고 소유 → 첫 등장 때 1번 생성, struct가 몇 번 재생성돼도 같은 인스턴스 유지.

class CartViewModel: ObservableObject { @Published var items: [Item] = [] }
struct CartView: View {
    @StateObject private var vm = CartViewModel()   // 소유·1번 생성
}

@ObservedObject — 참조 객체를 관찰만(소유 X)

역시 ObservableObject지만 생명주기 소유 안 함. 밖에서 만들어져 넘어온 객체를 받아 변화만 구독.

⭐ @StateObject vs @ObservedObject — 최대 함정

// ❌ 위험: 뷰가 만드는 객체를 @ObservedObject로
struct CartView: View {
    @ObservedObject var vm = CartViewModel()   // struct 재생성마다 vm 새로 생성!
}

뷰 struct는 계속 재생성되는데 @ObservedObject는 소유를 안 하니 재생성마다 CartViewModel()이 새로 만들어져 장바구니 초기화·네트워크 재요청·상태 소실. @StateObject는 첫 생성 1번만 하고 identity 노드에 소유·보관.

규칙: 객체를 만드는(소유하는) 뷰 → @StateObject. 그 객체를 받아 쓰는(관찰만) 자식 뷰 → @ObservedObject. (§11 "상태는 안정적 identity 소유자 노드에 둔다"와 동일 원리: @StateObject=소유, @ObservedObject=포인터)

@Observable — 신형(iOS 17+), 관찰 방식 자체를 바꿈

// 구형: class VM: ObservableObject { @Published var items = [] }
// 신형:
@Observable class CartViewModel {
    var items: [Item] = []      // @Published 불필요 — 저장 프로퍼티 자동 추적
}
struct CartView: View {
    @State private var vm = CartViewModel()   // 소유: @State ★
    var body: some View { CartRow(vm: vm) }
}
struct CartRow: View {
    let vm: CartViewModel                     // 관찰: 그냥 let ★ (읽으면 자동 추적)
    var body: some View { Text("\(vm.items.count)") }
}
struct EditView: View {
    @Bindable var vm: CartViewModel           // 양방향 바인딩
    var body: some View { TextField("제목", text: $vm.title) }
}

정리 — 언제 뭘 쓰나

상황구형(iOS 16-)신형(@Observable)
값 타입 로컬 상태@State@State
뷰모델을 내가 생성·소유@StateObject@State
뷰모델을 받아 관찰@ObservedObject그냥 let/var
받은 객체에 양방향 바인딩@ObservedObject + $vm.x@Bindable + $vm.x
클래스 정의ObservableObject + @Published@Observable

핵심 3줄: ① @State=값 소유 / @StateObject=참조 소유(1번 생성) / @ObservedObject=참조 관찰만. ② 최대 함정: 뷰가 만드는 객체를 @ObservedObject로 두면 재생성마다 초기화 → 만드는 뷰는 @StateObject. ③ @Observable(신형): @Published·ObservableObject 없이 자동+정밀 추적, 소유는 @State·관찰은 let·바인딩은 @Bindable.

16. 2026-07-18 후속 — 박스=힙 그래프스토리지 / 소유 원리 / 구형 coarse vs @Observable fine / @Bindable

Q1. "SwiftUI 관리 저장소(박스)" = identity 노드 그래프 스토리지? 힙?

둘 다 맞다. identity 노드에 묶인 저장 공간이자, 힙에 할당된다(SwiftUI 런타임 관리). 뷰 struct(스택/인라인일 수 있음)와 별개로 박스는 힙에 살아 struct 재생성을 견딘다. "박스 = identity 노드에 매달린 힙 저장소" — 같은 대상.

Q2. "1번 생성·유지"는 @State도 마찬가지 아닌가? (class vs struct 차이?)

맞다. @State·@StateObject 둘 다 identity(위치/id)가 안 바뀌면 유지되고, 지속 메커니즘은 동일(identity 노드 박스). 초기값이 1번만 쓰이는 것도 같다.

저장하는 것추가로 하는 일
@State값 또는 참조(구형 class 관찰은 못 함)
@StateObjectclass 인스턴스(참조)objectWillChange 구독 + 생명주기 소유

핵심 차이 = "값 vs 참조" + "@StateObject는 objectWillChange를 구독". 구형에서 class에 @State 대신 @StateObject가 필요했던 건 @State가 @Published 변화를 구독 못 해서(신형 @Observable은 프레임워크가 구독 대행 → @State로도 됨).

Q3. "생명주기를 소유하지 않음"이 무슨 말?

@ObservedObject는 그 객체를 언제 만들고 없앨지 책임지지 않는다. @StateObject="내가 1번 만들고 내 identity 사는 동안 살려둠"(소유). @ObservedObject="넘어온 객체를 참조·구독만, 생성·유지는 딴 데(보통 부모 @StateObject) 책임" → "create once, persist" 보장 없음. 그래서 @ObservedObject로 직접 만들면 재생성마다 새로 생김.

Q4. @Binding으로 ObservableObject를 받나?

받는 도구가 아니다. @Binding=값 타입 양방향 바인딩. ObservableObject 받기=@ObservedObject. @Observable 받기=그냥 let(관찰) 또는 @Bindable(양방향). @Binding var vm: SomeClass는 컴파일은 되나 참조(포인터)를 바꿔 끼우는 용도라 내부 @Published 변화를 구독 못 함 → 갱신 안 됨.

Q5. @StateObject는 박스에 소유, @ObservedObject는 안 함?

맞다. @StateObject=객체를 identity 노드 박스에 소유·보관(유지). @ObservedObject=박스에 소유 안 함, 객체는 밖(부모 @StateObject 등)에 살고 참조+구독만. 지속은 소유자(@StateObject) 몫. 그래서 실무: 만드는 곳=@StateObject, 받는 곳=@ObservedObject.

Q6. ⭐ "구독한 뷰 전부 재평가" — 안 쓰는 프로퍼티도? (구형 vs 신형)

객체 단위 구독(구형, 안 써도 갱신) vs 프로퍼티 단위 구독(신형, 읽은 것만). 이게 @Observable 최대 장점.

Q7. ⭐ "@State로 소유 / 그냥 let으로 관찰" 자세히

(a) 소유@State private var vm = VM(): @Observable 객체는 @StateObject가 아니라 @State로 소유. @State가 1번 생성 + 박스 보관(유지)을 담당(@StateObject의 소유·지속 역할). 왜 이제 @State로 되나? 구형엔 @State가 @Published 구독을 못 했지만, @Observable은 프로퍼티 읽는 시점에 프레임워크가 자동 관찰하므로 @State는 참조 보관만 하면 됨.

struct CartRow: View {
    let vm: CartViewModel                 // (b) 관찰 — 래퍼 없음
    var body: some View {
        Text("\(vm.items.count)")         // 이 "읽기"를 프레임워크가 감지 → items 바뀌면 이 뷰만 갱신
    }
}

(b) 관찰(받기) — 그냥 let vm: VM: @ObservedObject 없이 저장 프로퍼티로 받고, body에서 프로퍼티를 읽는 순간 "이 뷰는 그 프로퍼티에 의존" 등록(§13 getter 훅킹과 동일). 읽기가 곧 구독. → @Observable에선 래퍼(@StateObject/@ObservedObject)가 사라지고 "소유=@State, 관찰=그냥 프로퍼티".

Q8. @Bindable이 있어?

있다(iOS 17 Observation). @Observable 객체의 프로퍼티에 $로 양방향 바인딩할 때 쓴다. 그냥 let vm은 읽기(관찰)만; TextField·Toggle처럼 $가 필요하면 @Bindable.

struct EditView: View {
    @Bindable var vm: CartViewModel
    var body: some View { TextField("제목", text: $vm.title) }
}

Q9. "예전엔 @Binding으로 받았는데 17부턴 let/var면 돼?" (혼동 정리)

@Binding과 @ObservedObject를 섞어 기억하기 쉽다. 무엇을 받았느냐에 따라 다르다:

예전에 받은 것예전 방식iOS 17 @Observable
값(String/Int) 양방향@Binding var text그대로 @Binding(값 바인딩 불변)
ObservableObject 관찰@ObservedObject var vm그냥 let vm(읽기 관찰)
객체 프로퍼티에 $ 바인딩@ObservedObject + $vm.x@Bindable var vm + $vm.x

즉 "그냥 let/var면 돼?"의 답: 객체(ObservableObject→@Observable)를 받는 경우면 예. 하지만 값 바인딩이었으면 여전히 @Binding, 객체 프로퍼티에 $가 필요하면 @Bindable.

17. 2026-07-18 확인 — 래퍼/매크로/프로토콜 분류 · var/let 규칙 · 답변 프레이밍

Q1. iOS17부터 @State가 @Observable 변화를 구독? ObservableObject는 못 보고?

맞다. @State + @Observable → ✅(Observation이 추적). @State + 구형 ObservableObject → ❌ @Published 구독 못 함(@State는 참조만 들고 objectWillChange 안 들음) → 이땐 @StateObject 필요.

Q2. ObservableObject는 class? / Q3. ObservedObject는 class?

Q4. "body 재평가" = "body 재호출"?

같은 말. SwiftUI가 그 뷰의 body(계산 프로퍼티)를 다시 실행하는 것.

Q5. @Observable을 @StateObject로 받을 수 있나?

없다 — 컴파일 에러. @StateObject는 ObservableObject 채택 타입만 받는데, @Observable class는 ObservableObject를 채택하지 않는다(신형 Observation 프로토콜). @Observable 소유 = @State가 유일. @StateObject는 구형 ObservableObject 전용.

Q6. iOS17에 Observation 도입? "그래서 17 골랐다"고 그렇게 답해도?

사실: Observation은 iOS 17(WWDC 2023) 도입(@Observable·@Bindable, SwiftData도). 단 "그래서 17을 골랐다"는 원인-결과를 뒤집지 마라. 배포 타깃은 보통 사용자 기기 분포·필요 API로 정한다. 정직한 프레이밍:

"최소 지원 버전이 iOS 17이라 그 덕에 Observation·SwiftData 같은 신규 API를 쓸 수 있었다. 17을 고른 주된 이유는 [실제 이유]이고, Observation 채택은 그 결과로 얻은 이점이었다." → 대개 "17 지원 → Observation 사용 가능"이 맞는 순서. 실제 이유가 정말 Observation이 아니면 지어내지 말 것(더 파고들면 무너짐).

Q7. @Observable / ObservableObject / @ObservedObject / @Binding / @Bindable 모두 프로퍼티 래퍼?

아니다. 셋만 래퍼.

이름정체붙는 곳
@Observable매크로class 정의
ObservableObject프로토콜(@ 없음!)class가 채택
@ObservedObject프로퍼티 래퍼뷰 프로퍼티
@Binding프로퍼티 래퍼뷰 프로퍼티
@Bindable프로퍼티 래퍼뷰 프로퍼티

@Observable은 코드를 생성하는 매크로(프로퍼티를 감싸는 게 아님). ObservableObject는 프로토콜이라 원래 @도 안 붙는다(class VM: ObservableObject). @ObservableObject라는 래퍼는 없다.

Q8·Q9. var/let 규칙 — @Bindable·@ObservedObject를 let으로?

프로퍼티 래퍼(@ObservedObject·@Bindable·@State·@StateObject)는 전부 var 필수. let으로 하면 "property wrapper can only be applied to a 'var'" 에러. let이 되는 건 래퍼 없는 순수 프로퍼티 — @Observable 객체를 그냥 받을 때뿐.

let vm: CartViewModel              // ✅ @Observable 관찰(래퍼 없음, 재할당 안 하니 let 권장)
@Bindable var vm: CartViewModel    // ✅ 양방향 필요 시 (var 필수)
@ObservedObject var vm: VM         // ✅ 구형, var 필수
@ObservedObject let vm: VM         // ❌ 컴파일 에러

객체는 참조 타입이라 var여도 복사되는 게 아니니 걱정 없음.

관련 문서

any / some / Generic / Dispatch 쉽게 이해하기
Protocol Oriented Programming Master