핵심 비교
- 값 타입(`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, 성능, 동시성 이슈까지 훨씬 일관되게 다룰 수 있다.