← Mobile Foundation 인터뷰 4 / 13

04 · Struct vs Class

의미론 → CoW → 객체 헤더 → 메모리 계층과 캐시 지역성
진행률
0 / 0 완료
면접에서 나오는 형태
  1. struct와 class는 무엇을 기준으로 고르나요?
  2. "struct가 더 빠르다"는 말은 항상 맞나요?
  3. Array는 struct인데 복사할 때 왜 안 느린가요?
L1 개념
복사냐 공유냐 — 성능이 아니라 의미론이 먼저다
L2 설계
기본은 struct. class는 identity·상속·ObjC 상호운용이 필요할 때. Swift 6에서는 Sendable 차이가 결정적
L3 구현
class는 힙 + 16바이트 헤더 + ARC. struct는 인라인. CoW는 isKnownUniquelyReferenced로 구현
L4 CS
메모리 계층 · 캐시 라인 · 포인터 추격 — 성능 차이의 진짜 출처는 할당이 아니라 캐시 미스

30초 답변 🔥 먼저 이 문장

핵심은 의미론이지 성능이 아닙니다.
struct는 값 의미론 — 대입/전달하면 복사되고, 각자가 자기 것만 봅니다. class는 참조 의미론 — 같은 것을 가리킵니다. 그래서 identity(===)가 있고, 한쪽에서 바꾸면 다른 쪽에도 보입니다.

그래서 "이 데이터는 복사되는 게 맞는가, 공유되는 게 맞는가"를 먼저 정하고 성능은 그다음입니다. 다만 Swift 6부터는 여기에 하나가 더 붙습니다 — 값 타입은 Sendable을 거의 공짜로 얻고, class는 그렇지 않습니다.

L1개념 — 차이의 전체 지도

structclass
의미론값 (복사)참조 (공유)
저장 위치인라인 — 스택 또는 포함하는 객체 안항상 힙
인스턴스 오버헤드없음객체 헤더 16바이트 (64비트)
수명 관리스코프 — 컴파일 타임에 결정ARC — 런타임 카운팅
identity없음 (=== 불가)있음
상속불가 (프로토콜로 대체)가능
변경var + mutatinglet이어도 프로퍼티 변경 가능
Sendable프로퍼티가 모두 Sendable이면 자동 추론(비-public)final + 전부 immutable 이어야 수동 채택 가능
deinit없음있음
⚠️ 정확히 — Sendable 자동 추론의 범위

"struct는 자동으로 Sendable"은 절반만 맞습니다. 자동 추론은 public이 아닌 타입에만 적용됩니다. public struct는 모듈 밖에서 쓰이므로, 라이브러리 저자가 의도적으로 약속해야 합니다 — 명시적으로 : Sendable을 적어야 하죠.

public 경계에서 달라진다
struct Internal { var x: Int }                 // ✅ 자동으로 Sendable
public struct Public { public var x: Int }     // ❌ 자동 아님
public struct Public2: Sendable { public var x: Int }  // ✅ 명시해야 한다

L2언어 설계 — 선택 기준과 CoW

먼저: 왜 Swift는 값 타입을 1급으로 올렸나

"기본은 struct"라는 권고는 취향이 아니라 Objective-C 시절의 특정 버그 유형에 대한 대응입니다. 여기를 설명할 수 있으면 설계 의도를 안다는 게 드러납니다.

Swift가 없애려 한 것 — 방어적 복사(defensive copy) 관행
// ── Objective-C: 참조가 기본이라 "넘긴 뒤"를 통제할 수 없다 ──
- (void)configureWith:(NSMutableArray *)items {
    self.items = items;          // 💥 호출자가 나중에 items를 바꾸면 내 상태도 바뀐다
}
// 그래서 Cocoa 전반에 이 관행이 생겼다:
- (void)configureWith:(NSArray *)items {
    self.items = [items copy];   // 방어적 복사 — "혹시 모르니 일단 복사"
}
// @property (copy) NSString *name;  ← copy 어트리뷰트가 흔한 이유가 이것

// 문제점
//   ① 개발자가 매번 기억해야 한다 (잊으면 조용히 버그)
//   ② 필요 없을 때도 복사한다 (항상 비용을 낸다)
//   ③ 얕은 복사라서 중첩 구조는 여전히 샌다

// ── Swift: 복사를 기본값으로 바꿔 이 셋을 한 번에 없앤다 ──
func configure(with items: [Item]) {
    self.items = items           // ✅ 이미 값이다. 방어적 복사가 불필요하다
}

그런데 "복사가 기본"은 곧바로 성능 반론에 부딪힙니다. 원소 10만 개 배열을 함수에 넘길 때마다 복사하면 쓸 수 없는 언어가 되죠. Swift의 판단은 "의미론은 값으로 고정하고, 비용은 표준 라이브러리가 흡수한다"였습니다 — 그 흡수 장치가 바로 다음에 볼 CoW입니다.

🧠 설계 의도 세 문장으로 정리

문제 — 참조가 기본이면 "넘긴 뒤 남이 바꾸는" 버그가 생기고, 방어적 복사라는 수동 관행으로 막아야 했다.
선택 — 복사를 기본값으로 만들어 그 관행 자체를 없앤다. 안전을 개발자 기억이 아니라 언어 규칙에 둔다.
대가와 보완 — 복사 비용은 CoW로 지연시킨다. 즉 계약은 값, 구현은 공유다.

그리고 이 선택은 10년 뒤에 배당금을 냅니다. Swift 6가 데이터 경합을 컴파일 타임에 막을 수 있는 이유가 "값 타입은 공유되지 않으므로 Sendable을 자동으로 얻는다"이기 때문입니다. 2014년의 값 타입 결정이 2024년의 동시성 안전을 가능하게 한 셈이고, 이걸 연결해서 말하면 "특징 나열"이 "설계 서사"가 됩니다.

근거 수준에 대해 — Apple의 Choosing Between Structures and Classes"기본은 struct"라는 권고와 그 예외를 공식 문서로 밝히고 있습니다. 반면 위의 "방어적 복사 관행을 없애려 했다"는 인과는 Apple이 문서로 못 박은 문장이 아니라, 값 의미론 도입의 배경으로 널리 받아들여지는 해석입니다. 면접에서는 "공식 권고는 이렇고, 그 배경은 이렇게 이해하고 있습니다"로 층을 나눠 말하는 편이 정확합니다.

Apple의 공식 선택 기준

  1. 기본은 struct.
  2. ObjC 상호운용이 필요하면 class.
  3. identity 제어가 필요하면 class — 공유되는 하나의 인스턴스여야 할 때.
  4. 상속이 필요하면 class — 단, 프로토콜 + 컴포지션을 먼저 검토.

Copy-on-Write — "struct인데 왜 안 느린가"

Array·String·Dictionary·Set은 struct지만 내부에 힙 버퍼 참조를 들고 있습니다. 복사 시점엔 참조만 복사하고(O(1)), 쓰기 시점에 유일 소유자가 아니면 그때 실제로 복사합니다.

① var b = a a b buffer [1,2,3] refCount = 2 복사 안 함 — O(1) ② b.append(4) 시도 isKnownUniquelyReferenced? refCount == 2 → 나 혼자가 아니다 지금 복사한다 ③ 결과 a b [1,2,3] refCount = 1 [1,2,3,4] refCount = 1 이제 각자 자기 버퍼 "복사하는 척하다가, 진짜 필요할 때만 복사한다" — 지연(laziness)의 전형
값 의미론은 보장하고 복사는 미룬다. 사용자에게는 항상 복사된 것처럼 보이지만, 실제 복사는 필요한 순간에만 일어난다.
직접 만들 때 — 핵심은 유일 소유 판정 (Swift)
final class Storage {
    var data: [Int]
    init(_ d: [Int]) { data = d }
    func copy() -> Storage { Storage(data) }
}

struct Box {
    private var storage: Storage
    init(_ d: [Int]) { storage = Storage(d) }

    var values: [Int] { storage.data }        // 읽기는 그냥 통과

    mutating func append(_ v: Int) {
        if !isKnownUniquelyReferenced(&storage) {
            storage = storage.copy()          // 나만 쓰는 게 아니면 그때 복사
        }
        storage.data.append(v)
    }
}
📝 String의 예외 — 항상 힙인 것도 아니다

Stringsmall string optimization이 있습니다. 충분히 짧은 문자열(UTF-8 기준 15바이트 이하)은 힙 버퍼 없이 String 값 안에 직접 들어갑니다. 그래서 짧은 문자열은 할당도, ARC도, CoW 검사도 없습니다. "String은 항상 힙"이 아닌 게 정확합니다.

L3구현 — 메모리에서 실제로 무엇이 다른가

struct Point { var x, y: Int } var a = Point(1,2) ; var b = a STACK (또는 포함 객체 안) a x=1 y=2 b x=1 y=2 16바이트 × 2 — 완전히 별개 b.x = 99 해도 a.x 는 1 할당 없음 · ARC 없음 주소가 연속이라 캐시에 잘 올라간다 class Node { var v: Int } let n1 = Node() ; let n2 = n1 STACK — 포인터만 n1 → 0x1F40 n2 → 0x1F40 HEAP type metadata 포인터 (8B) refCount (8B) — 여기가 ARC 대상 ↓ 이 아래부터 v: Int 데이터 8B를 위해 헤더 16B + 힙 할당 + ARC
class 인스턴스는 데이터 앞에 16바이트 헤더가 붙는다. Int 하나 담은 클래스가 24바이트 이상을 쓰는 이유이고, 작은 객체를 수만 개 만들 때 문제가 되는 지점이다.

MemoryLayout — size / stride / alignment

셋이 왜 다른가, 그리고 필드 순서가 왜 크기를 바꾸는가 (Swift 6.2.1 실측)
struct A { var flag: Bool; var num: Int64; var flag2: Bool }
struct B { var flag: Bool; var flag2: Bool; var num: Int64 }

MemoryLayout<A>.size       // 17  — Bool(1) + 패딩7 + Int64(8) + Bool(1)
MemoryLayout<A>.alignment  // 8   — 가장 엄격한 멤버(Int64)를 따른다
MemoryLayout<A>.stride     // 24  — 17을 8의 배수로 올림 (배열에서 다음 원소까지)

MemoryLayout<B>.size       // 16  — Bool(1) + Bool(1) + 패딩6 + Int64(8)
MemoryLayout<B>.stride     // 16  — 이미 8의 배수라 꼬리 패딩이 없다

// ★ size와 stride의 차이가 여기서 드러난다
//   size   : 마지막 유효 바이트까지 — 꼬리 패딩을 포함하지 않는다 (A = 17)
//   stride : size를 alignment의 배수로 올림한 값 — 꼬리 패딩 포함 (A = 24)
//   배열의 원소 간격을 정하는 건 size가 아니라 stride다.
//
// 선언 순서만 바꿔서 stride가 24 → 16 으로 줄었다.
// 원소 100만 개 배열이면 8MB 차이다.
📝 왜 정렬이 필요한가 (여기가 CS로 넘어가는 문)

CPU는 메모리를 바이트 단위가 아니라 워드 단위로 읽습니다. 8바이트 값이 8의 배수 주소에 있으면 한 번에 읽지만, 걸쳐 있으면 두 번 읽고 이어 붙여야 합니다. 일부 아키텍처는 아예 정렬되지 않은 접근을 오류로 처리합니다. 그래서 컴파일러가 패딩을 넣어 정렬을 맞춥니다 — 공간을 써서 시간을 사는 것입니다.

L4CS 근본 — 진짜 성능 차이는 캐시에서 난다

"class는 힙 할당이라 느리다"는 흔한 설명인데, 절반만 맞습니다. 할당 비용도 있지만, 실무에서 훨씬 크게 작용하는 건 메모리 계층입니다.

① 메모리 계층 — 숫자를 알면 판단이 바뀐다

레지스터 ~1 사이클 L1 캐시 ~4 사이클 L2 캐시 ~12–20 사이클 L3 / SLC ~40 사이클 메인 메모리 (DRAM) 여기까지 내려가면 게임 끝 ~200+ 사이클 빠름 느림 ※ 사이클 수는 아키텍처마다 다른 대략치 — 중요한 건 한 단계 내려갈 때마다 자릿수가 바뀐다는 사실
L1 히트와 DRAM 접근은 수십 배 차이 난다. 그래서 "연산을 몇 번 하느냐"보다 "데이터가 캐시에 있느냐"가 성능을 지배하는 경우가 많다.
📝 단위를 바꿔 보면 — 시간으로도 같은 이야기

위는 사이클 기준이고, ios-cs 12 · CPU와 성능 Q2는 같은 계층을 시간 기준으로 정리합니다 — 레지스터 ≈0.3ns, L1~L3 ≈1–20ns, DRAM ≈50–100ns, SSD 수십~수백µs. 둘은 같은 사실의 다른 표현이고(사이클 ÷ 클럭 = 시간), 면접에서는 정확한 숫자보다 "단계마다 자릿수가 바뀐다"를 말하는 게 안전합니다.

한 가지 iOS 특수 사정도 알아 두면 좋습니다 — Apple Silicon은 통합 메모리(Unified Memory)라 CPU·GPU·Neural Engine이 같은 물리 DRAM 풀을 공유합니다. CPU↔GPU 복사 비용이 줄어드는 대신 공유 대역폭을 아끼는 것이 중요해지고, 이건 11번의 렌더링 비용과 바로 이어집니다.

② 캐시 라인과 공간 지역성

CPU는 메모리를 바이트가 아니라 캐시 라인 단위로 가져옵니다. 라인 크기는 아키텍처마다 다른데, x86은 보통 64바이트, Apple Silicon은 128바이트입니다. 즉 Int 하나(8바이트)를 읽어도 그 주변 120바이트가 함께 캐시에 올라옵니다.

여기서 struct 배열과 class 배열의 결정적 차이가 나옵니다.

[Point] — struct 배열 p0p1p2 p3p4 캐시 라인 한 번에 여러 개가 딸려 온다 데이터가 연속 → 순회하면 캐시 히트 프리페처가 다음 라인까지 미리 가져온다 [Node] — class 배열 ptrptrptr ptrptr n0n1n2 n3n4 힙 여기저기 — 접근할 때마다 캐시 미스 (포인터 추격)
포인터 추격(pointer chasing): class 배열은 포인터 배열이라, 실제 데이터를 읽으려면 매번 힙의 임의 위치로 점프해야 한다. 프리페처도 예측하지 못하고, 미스가 날 때마다 200 사이클씩 지불한다.
같은 로직, 다른 자료구조 — 차이의 출처는 할당이 아니라 순회다
struct PointS { var x: Double; var y: Double }
final class PointC { var x = 0.0; var y = 0.0 }

let structs = (0..<1_000_000).map { _ in PointS(x: 1, y: 2) }
let classes = (0..<1_000_000).map { _ in PointC() }

// 순회 — 여기가 진짜 차이가 나는 곳
var sum = 0.0
for p in structs { sum += p.x }   // 연속 메모리 → 캐시 라인마다 여러 개 처리
for p in classes { sum += p.x }   // 포인터 → 힙 임의 위치 → 미스 + ARC까지

// 주의: 실제 수치는 요소 크기·접근 패턴·최적화 수준에 따라 크게 달라진다.
// 면접에서는 배수를 말하지 말고 "왜 차이가 나는가"를 말하는 게 안전하다.

③ 참조 카운팅이 멀티코어에서 비싼 진짜 이유

retain/release원자적 증감입니다. 그런데 원자적 연산의 비용은 명령어 몇 개가 아니라 캐시 일관성 트래픽에서 나옵니다.

여기서 "값 타입을 기본으로"라는 조언이 성능 조언이자 동시성 조언으로 합쳐집니다. 값 타입은 공유되지 않으니 refCount 경쟁이 없고, 그래서 Sendable도 자동으로 얻습니다 — 07번 Thread Safety와 만나는 지점입니다.

④ CoW는 OS가 먼저 쓰던 기법이다

Copy-on-Write는 Swift의 발명이 아니라 운영체제의 고전 기법입니다. fork()로 프로세스를 복제할 때 메모리 전체를 복사하지 않고, 페이지를 공유한 뒤 읽기 전용으로 표시합니다. 누군가 쓰려고 하면 그때 페이지 폴트를 일으켜 그 페이지만 복사하죠.

OS의 fork COWSwift의 CoW
단위메모리 페이지 (보통 4KB / 16KB)타입의 힙 버퍼
공유 여부 판정페이지 테이블 + MMU 보호 비트isKnownUniquelyReferenced (refCount)
복사 트리거쓰기 시 페이지 폴트 (하드웨어)쓰기 시 명시적 검사 (소프트웨어)
아이디어동일 — "복사한 것처럼 보이게 하되, 실제 복사는 쓰기 순간까지 미룬다"
🧠 L4 그래서 struct는 "복사한다"가 아니라 "복사한 것처럼 보인다"

이 절의 결론은 struct vs class 판단에 직접 걸립니다. 값 의미론은 계약이고, 복사는 구현입니다. 사용자에게는 언제나 "복사됐다"가 보장되지만, 실제 memcpy공유가 깨지는 순간에만 일어납니다.

그래서 "struct는 복사 비용 때문에 느리다"는 CoW를 쓰는 타입에는 성립하지 않습니다. 비용이 붙는 쪽은 CoW가 없는 큰 struct(고정 크기 배열을 품은 값 타입 등)이고, 그건 inout·borrowing으로 따로 다뤄야 합니다. 판단할 때 물어야 할 것은 "struct냐 class냐"가 아니라 "이 타입에 CoW가 있는가"입니다.

참고 — 이 "미뤄서 필요할 때만 한다" 전략은 12번(레이아웃 무효화)에서 다른 층으로 한 번 더 나옵니다. 전체 정리는 거기서 합니다.

실무 선택 프레임

대상선택이유
모델 · DTO · 좌표 · 설정struct복사가 맞다. Sendable 자동. 캐시 친화적
화면 상태struct스냅샷·diff가 가능해진다 (01번)
캐시 · 네트워크 클라이언트 · 코디네이터class 또는 actor하나를 공유해야 한다 — identity가 본질
UIKit 컴포넌트class선택지 없음
거대한 값 + 잦은 전달측정 후 결정복사 비용 vs 간접 참조 비용. inout·borrowing도 검토

CS 정본으로 더 내려가기

이 페이지의 L4는 면접 답변에 필요한 깊이까지만 팝니다. 같은 주제를 CS 원리 → iOS 매핑 → 재현·측정 순서로 끝까지 파는 것은 허브의 iOS 개발자 CS 로드맵이고, 대응 챕터는 아래입니다.

경험으로 말하기

🎤 "바꿔서 빨라졌다"가 아니라 "왜 그 방향이었나"

"class를 struct로 바꿔서 빨라졌습니다."

"차트가 데이터 포인트를 final class로 들고 있었고, 수만 개를 매 프레임 순회했습니다. Time Profiler에서 순회 루프 자체가 아니라 swift_retain/swift_release가 상위에 올라와 있었습니다. 포인트에는 identity가 필요 없었고 공유되지도 않아서 struct로 바꿨습니다. ARC 호출이 사라졌고, 배열이 연속 메모리가 되면서 순회 자체도 빨라졌습니다. 다만 포인트를 담는 컨테이너는 class로 남겼습니다 — 여러 화면이 같은 데이터셋을 봐야 했거든요."

여기엔 측정 → 원인 → 의미론적 정당화 → 안 바꾼 부분까지 들어 있습니다.

꼬리 질문 대비

L2 "struct가 항상 빠르다"는 맞나요?

아닙니다. 이 답을 못 하면 감점입니다.

  • 큰 struct를 자주 복사하면 memcpy 비용이 그대로 듭니다. 수백 바이트짜리 struct를 함수 인자로 계속 넘기면 포인터 하나 넘기는 class보다 비쌉니다.
  • 완화 수단이 있습니다 — 컴파일러가 상당수 복사를 제거하고, inout·borrowing으로 명시적으로 빌려 쓸 수 있으며, 내부에 CoW 저장소를 두는 방법도 있습니다.
  • 반대로 class가 유리한 경우도 명확합니다 — 큰 데이터를 여러 곳이 공유해야 하고 변경이 드물 때.

정확한 진술: "struct는 할당과 ARC를 없애고 지역성을 좋게 하지만, 복사 비용을 만듭니다. 어느 쪽이 이기는지는 크기와 접근 패턴이 정하고, 그건 측정해야 압니다."

쉽게: 서류를 복사해서 나눠주기(struct) vs 원본 위치를 알려주기(class). 서류가 한 장이면 복사가 낫고, 500장이면 위치를 알려주는 게 낫습니다.
L3 struct 안에 class 프로퍼티가 있으면 값 타입인가요?

이게 실무에서 진짜 사고를 내는 지점입니다. 타입은 struct지만 값 의미론이 깨집니다.

❌ struct인데 공유되는 상태
final class Box { var value = 0 }
struct Wrapper { var box = Box() }      // struct 안에 참조 타입

var a = Wrapper()
var b = a                // struct 복사 — 하지만 box 는 "포인터가" 복사된다
b.box.value = 99
print(a.box.value)       // 99 ⚠️ a도 바뀌었다!

복사된 것은 참조뿐이고 가리키는 객체는 하나입니다. 이걸 얕은 복사(shallow copy)라고 하고, 값 의미론을 원한다면 깊은 복사를 직접 구현해야 합니다(CoW 패턴).

Swift 6에서는 이게 컴파일 에러로도 나타납니다BoxSendable이 아니므로 WrapperSendable이 아니고, 격리 경계를 넘길 수 없습니다. 컴파일러가 "이건 진짜 값 타입이 아니다"를 알려 주는 셈입니다.

쉽게: 명함을 복사했는데 거기 적힌 집 주소는 그대로인 것과 같습니다. 명함은 두 장이 됐지만 집은 하나라, 한 사람이 가구를 옮기면 다른 사람이 가도 옮겨져 있습니다.
L4 class가 힙에 있어서 느린 거라면, 힙 할당만 빠르게 하면 되지 않나요?

좋은 질문이고, 답은 "할당은 이미 꽤 빠르지만 그게 주된 비용이 아니다"입니다. 세 가지가 겹칩니다.

  • ① 할당 비용 — malloc은 크기별 free list와 메타데이터를 관리합니다. 요즘 할당자는 빠르지만 공짜는 아니고, 멀티스레드에서는 락 또는 스레드 로컬 캐시가 필요합니다.
  • ② ARC 비용 — 원자적 refCount 증감 + 멀티코어에서 캐시 라인 소유권 왕복. 이게 앞서 본 MESI 트래픽입니다.
  • ③ 지역성 손실이게 보통 가장 큽니다. 할당을 0으로 만들어도, 데이터가 힙에 흩어져 있으면 순회할 때마다 캐시 미스가 납니다. 미스 하나가 200 사이클이면, 할당 몇 나노초를 아끼는 것보다 훨씬 큽니다.

그래서 고성능 코드의 방향이 "할당을 빠르게"가 아니라 "데이터를 연속으로 배치"인 겁니다. 게임 엔진의 SoA(Structure of Arrays)나 ECS 아키텍처가 극단적으로 이 방향을 밀어붙인 예입니다.

쉽게: 책 100권을 읽어야 하는데, 책장 한 칸에 나란히 있으면 한 번 가서 팔로 쓸어 오면 됩니다(struct 배열). 도서관 여기저기 흩어져 있으면 100번 왕복해야 하고요(class 배열). 책을 꺼내는 속도(할당)를 올려도, 왕복 거리가 문제면 소용이 없습니다.

쉽게 이해하기

struct는 복사해서 나눠 주는 종이입니다. 내가 내 종이에 낙서해도 네 종이는 멀쩡해요. class는 화이트보드 위치를 알려 주는 것입니다. 우리 둘 다 같은 보드를 보니까, 내가 지우면 너한테도 지워집니다.

그런데 "종이는 복사하니까 느리겠네?"라고 생각하기 쉬운데, Swift는 여기서 꾀를 부립니다. 일단 "복사했다고 말만 해 두고" 실제로는 같은 종이를 봅니다. 그러다 누군가 연필을 드는 순간에만 진짜로 복사합니다. 이게 CoW예요. 대부분은 읽기만 하니까, 진짜 복사는 생각보다 훨씬 적게 일어납니다.

그리고 여기가 CS로 이어지는 지점입니다. 컴퓨터가 느려지는 진짜 이유는 계산이 많아서가 아니라 데이터를 가지러 멀리 가야 해서인 경우가 많습니다. CPU 바로 옆 서랍(캐시)에 있으면 순식간이고, 창고(메인 메모리)까지 가야 하면 50배 넘게 걸립니다.

종이 100장이 한 뭉치로 묶여 있으면(struct 배열) 한 번에 서랍으로 가져올 수 있습니다. 그런데 "창고 A-3칸, B-7칸, C-1칸..." 위치 목록만 들고 있으면(class 배열) 100번 창고를 왕복해야 해요. 이게 struct 배열이 빠른 진짜 이유입니다 — 할당을 안 해서가 아니라, 가지러 갈 거리가 짧아서.

🔑 한 문장

먼저 복사냐 공유냐로 정하고, 성능이 문제되면 할당이 아니라 지역성을 본다. 그리고 Swift 6에서는 이 선택이 Sendable을 공짜로 받느냐까지 결정한다.

설계 근거 · 1차 자료

이 페이지의 "왜 그렇게 설계했나" 주장은 아래에서 나왔습니다. 면접에서 근거를 물으면 이 이름들을 대면 됩니다.

📌 각 자료가 뒷받침하는 것
  • Apple — "기본은 struct"라는 권고와 그 네 가지 예외의 1차 출처
  • WWDC16 — 할당·참조 카운팅·메모리 배치를 축으로 한 성능 설명의 원전