오늘은 Struct와 Class에 대한 차이점을 다시 한번 공부해보겠습니다. 예전에 학습했던 내용이지만 실무를 거치면서 더 깊이 알아보고 싶어서 정리해보려고 합니다!
Swift를 공부하다 보면 가장 먼저 만나는 큰 산 중 하나가 바로 Struct(구조체)와 Class(클래스)의 차이일 것입니다. 둘 다 데이터를 담는 그릇이라는 점에서 비슷해 보이지만, 내부 동작 방식과 성능에 결정적인 차이를 만듭니다.
이 글에서는 Struct와 Class의 기본적인 차이점에서 시작해, "그래서 메모리 상에서는 어떤 일이 벌어지는 걸까?"라는 근본적인 궁금증을 해결해가는 과정을 담아보았습니다.
먼저, 두 타입이 공유하는 기능과 명확한 차이점을 짚고 넘어가겠습니다.
| 구분 | Class (클래스) | Struct (구조체) |
|---|---|---|
| 타입 종류 | 참조 타입 (Reference Type) | 값 타입 (Value Type) |
| 상속 | ✅ 가능 | ❌ 불가능 |
| 메모리 영역 | 힙 (Heap) | 스택 (Stack) |
| 메모리 관리 | ARC (자동 참조 계산) | 스코프(Scope) 종료 시 해제 |
소멸자(deinit) | ✅ 사용 가능 | ❌ 사용 불가능 |
위 표를 보고 나니 몇 가지 질문이 꼬리를 물고 이어졌습니다.
Class는 ARC로 메모리를 관리한다는데, 정말로 ARC가 동작하는 걸 눈으로 볼 수 있을까?- 힙(Heap) 메모리와 스택(Stack) 메모리는 대체 뭘까? 눈에 보이지 않는 이 공간들의 실체가 궁금하다.
- 객체의 메모리 크기는 언제, 어떻게 결정되는 걸까?
Struct는 함수가 끝나면 사라진다는데, 왜 계속 살아있는 것처럼 보일까?- 왜 SwiftUI의
View는Class가 아닌Struct로 만들어졌을까?
이 궁금증들을 하나씩 해결해 보겠습니다.
가장 핵심적인 질문, 스택과 힙의 정체부터 알아보죠. 이 둘은 별개의 하드웨어가 아니라, 우리가 사용하는 RAM을 프로그램이 어떻게 효율적으로 사용할지에 대한 '논리적인 규칙'이자 '관리 방식'입니다.
RAM이라는 거대한 '도서관'에 비유해 볼까요?
Struct, Int, Double 같은 값 타입(Value Type) 데이터가 주로 저장됩니다.Class로 만든 참조 타입(Reference Type) 객체처럼, 프로그램 전반에 걸쳐 사용되어야 하거나 크기가 큰 데이터를 저장합니다.이제 스택과 힙을 알았으니, 나머지 궁금증들을 풀어보죠.
결론부터 말하면, 컴파일 타임(Compile Time)에 결정됩니다.
컴파일러는 우리가 작성한 코드를 보고 Struct나 Class의 설계도에 어떤 프로퍼티가 있는지 분석하여, 인스턴스 하나를 만들 때 필요한 총 메모리 크기를 미리 계산해 둡니다.
// MARK: - 메모리 크기 확인 예제
struct Point {
var x: Double // 8바이트
var y: Double // 8바이트
}
// Point의 크기는 8 + 8 = 16바이트로 컴파일 시점에 확정됩니다.
class Size {
var width: Double = 0.0 // 8바이트
var height: Double = 0.0 // 8바이트
}
// Size '객체'의 크기는 8 + 8 = 16바이트 (+ α 관리 정보)로 계산됩니다.
// 하지만 Size '변수'는 이 객체를 가리키는 '주소'만 저장합니다.
// MemoryLayout을 통해 실제 크기를 확인할 수 있습니다.
print("Point(Struct)의 크기: \(MemoryLayout<Point>.size) 바이트")
// 출력: Point(Struct)의 크기: 16 바이트
print("Size(Class) 변수의 크기: \(MemoryLayout<Size>.size) 바이트")
// 출력: Size(Class) 변수의 크기: 8 바이트 (64비트 환경에서 주소값의 크기)
Struct 변수는 16바이트짜리 데이터 자체를 가집니다.Class 변수는 힙에 있는 16바이트짜리 객체를 가리키는 8바이트짜리 주소를 가집니다.이는 값 타입의 '복사(Copy)' 특성 때문입니다.
함수 내에서 Struct 인스턴스를 생성하면 그 값은 함수의 스택 공간에 저장됩니다. 함수가 이 값을 반환(return)할 때, 값 그 자체가 아닌 '복사본'을 만들어 넘겨줍니다. 원본은 함수 종료와 함께 스택에서 사라지지만, 복사본이 새로운 변수에 할당되었기 때문에 계속 사용할 수 있는 것이죠.
이 모든 지식을 바탕으로 마지막 질문에 답할 수 있습니다. SwiftUI가 View를 Class가 아닌 Struct로 설계한 데에는 명확한 이유가 있습니다.
⚡️ 엄청난 속도 (Performance): View는 상태가 바뀔 때마다 계속해서 새로 그려집니다(실제로는 버리고 새로 만듭니다). 스택 기반의 Struct는 생성과 파괴 비용이 거의 0에 가까울 정도로 저렴하기 때문에, 이러한 방식에 완벽하게 부합합니다.
🔒 예측 가능하고 안전한 상태 (Predictability): View는 값 타입이므로 어딘가에서 나도 모르게 데이터가 변경될 위험(Side-Effect)이 없습니다. View는 오직 주어진 상태(@State 등)에 의해서만 결정됩니다. UI = f(State) 라는 선언적 UI의 핵심 공식이 지켜지는 것이죠.
💡 값으로서의 UI (UI as a Value): View는 UI 그 자체가 아니라, 특정 상태일 때 UI가 어때야 하는지에 대한 '설명서'이자 '값'입니다. SwiftUI는 이 가벼운 설명서들을 비교(Diffing)하여 실제 화면에서 변경이 필요한 부분만 최소한으로 업데이트합니다.
Struct와 Class의 차이는 단순히 '상속이 되냐, 안 되냐'의 문제가 아니었습니다. 메모리가 어떻게 관리되고, 데이터가 어떻게 전달되는지에 대한 근본적인 차이를 이해하는 것이었습니다.
Struct.Class.이 원칙을 이해하고 상황에 맞는 도구를 선택하는 것이 더 안정적이고 효율적인 Swift 코드를 작성하는 첫걸음이 될 것입니다.