핵심 비교
- 값 타입(`struct`, `enum`, `tuple`): 대입/전달 시 값이 복사된 것처럼 독립 상태를 가진다.
- 참조 타입(`class`, `closure`): 대입/전달 시 동일 인스턴스를 공유한다.
- 값 타입은 공유 가변 상태를 줄이기 쉽고, 참조 타입은 생명주기와 동기화 관리가 중요하다.
이 주제의 핵심은 스택/힙 암기가 아니라 동작 의미(semantics)다. 값 타입은 "복사된 값"처럼, 참조 타입은 "같은 실체 공유"처럼 동작한다. 메모리 위치는 이 동작을 구현하기 위한 결과다.
기록 없음
struct CounterValue {
var n: Int
}
final class CounterRef {
var n: Int
init(n: Int) { self.n = n }
}
var a = CounterValue(n: 1)
var b = a
b.n = 10
// a.n == 1, b.n == 10 (독립 상태)
let c = CounterRef(n: 1)
let d = c
d.n = 10
// c.n == 10, d.n == 10 (동일 인스턴스 공유)`Array`, `Dictionary`, `String`은 값 타입이지만 내부 저장 버퍼를 참조로 최적화한다. 읽기만 할 때는 버퍼를 공유하고, 쓰기 시점에만 복사해서 값 의미를 유지한다.
var x = [1, 2, 3]
var y = x // 내부 버퍼 공유 가능
y.append(4) // 쓰기 시점에 분리(copy)
// x == [1, 2, 3]
// y == [1, 2, 3, 4]값 타입도 escaping 클로저에 캡처되면 저장 수명 때문에 힙이 관여할 수 있다. 그래도 외부 관찰 동작은 값 의미를 따르는지(스냅샷/변수 캡처)로 판단해야 한다.
struct S { var n: Int }
var s = S(n: 1)
let snap = { [s] in print("snap:", s.n) }
let live = { print("live:", s.n) }
s.n = 9
snap() // 1 (캡처 시점 스냅샷)
live() // 9 (현재 변수 값 참조)| 상황 | 권장 | 이유 |
|---|---|---|
| 도메인 값(좌표, 금액, 응답 모델) | `struct` | 불변/독립 상태 설계가 쉽고 테스트가 단순해진다. |
| 공유 identity가 필요한 객체(세션, 코디네이터, 캐시) | `class` | 하나의 실체를 여러 곳에서 참조하고 수명을 제어해야 한다. |
| 상태 변경 전파가 빈번한 UI 바인딩 대상 | 상황별 선택 | 값 스냅샷 기반 갱신인지, 참조 공유 기반 관찰인지 먼저 결정한다. |
메모리 주소를 설명하는 것보다 상태 전파 범위를 설명할 수 있어야 한다.
"복사되는 모델인지, 공유되는 객체인지"를 먼저 설계하면, ARC, 성능, 동시성 이슈까지 훨씬 일관되게 다룰 수 있다.
핵심 기준은 값 의미론(value semantics) vs 참조 의미론(reference semantics)과 식별성(identity)이다.
아니오. `struct`를 복사하면 내부 `class`는 주소(참조)만 복사되어 두 복사본이 같은 인스턴스를 공유한다.
class Engine { var power = 100 }
struct Car { var engine: Engine }
let a = Car(engine: Engine())
var b = a // struct라 복사되지만
b.engine.power = 200
print(a.engine.power) // 200 (!!) — engine 주소가 공유됨