Struct와 Class, 무엇이 다르고 어떻게 선택할까? (feat. Stack & Heap 메모리)

피터·2025년 7월 24일

오늘은 Struct와 Class에 대한 차이점을 다시 한번 공부해보겠습니다. 예전에 학습했던 내용이지만 실무를 거치면서 더 깊이 알아보고 싶어서 정리해보려고 합니다!

Swift를 공부하다 보면 가장 먼저 만나는 큰 산 중 하나가 바로 Struct(구조체)와 Class(클래스)의 차이일 것입니다. 둘 다 데이터를 담는 그릇이라는 점에서 비슷해 보이지만, 내부 동작 방식과 성능에 결정적인 차이를 만듭니다.

이 글에서는 Struct와 Class의 기본적인 차이점에서 시작해, "그래서 메모리 상에서는 어떤 일이 벌어지는 걸까?"라는 근본적인 궁금증을 해결해가는 과정을 담아보았습니다.


🚀 Struct와 Class: 기본 공통점과 차이점

먼저, 두 타입이 공유하는 기능과 명확한 차이점을 짚고 넘어가겠습니다.

✨ 공통점

  • 프로퍼티(Properties)를 정의하여 값을 저장할 수 있습니다.
  • 메서드(Methods)를 정의하여 기능을 제공할 수 있습니다.
  • 생성자(Initializers)를 통해 초기 상태를 설정할 수 있습니다.
  • 익스텐션(Extensions)을 통해 기능을 확장할 수 있습니다.
  • 프로토콜(Protocols)을 채택하여 특정 기능을 구현하도록 강제할 수 있습니다.

💥 결정적인 차이점

구분Class (클래스)Struct (구조체)
타입 종류참조 타입 (Reference Type)값 타입 (Value Type)
상속✅ 가능❌ 불가능
메모리 영역힙 (Heap)스택 (Stack)
메모리 관리ARC (자동 참조 계산)스코프(Scope) 종료 시 해제
소멸자(deinit)✅ 사용 가능❌ 사용 불가능

🤔 여기서 시작된 궁금증들

위 표를 보고 나니 몇 가지 질문이 꼬리를 물고 이어졌습니다.

  1. Class는 ARC로 메모리를 관리한다는데, 정말로 ARC가 동작하는 걸 눈으로 볼 수 있을까?
  2. 힙(Heap) 메모리와 스택(Stack) 메모리는 대체 뭘까? 눈에 보이지 않는 이 공간들의 실체가 궁금하다.
  3. 객체의 메모리 크기는 언제, 어떻게 결정되는 걸까?
  4. Struct는 함수가 끝나면 사라진다는데, 왜 계속 살아있는 것처럼 보일까?
  5. 왜 SwiftUI의 ViewClass가 아닌 Struct로 만들어졌을까?

이 궁금증들을 하나씩 해결해 보겠습니다.


🏛️ 메모리 파헤치기: 스택(Stack)과 힙(Heap)의 실체

가장 핵심적인 질문, 스택과 힙의 정체부터 알아보죠. 이 둘은 별개의 하드웨어가 아니라, 우리가 사용하는 RAM을 프로그램이 어떻게 효율적으로 사용할지에 대한 '논리적인 규칙'이자 '관리 방식'입니다.

RAM이라는 거대한 '도서관'에 비유해 볼까요?

🥞 스택(Stack): '긴급 참고 서가'

  • 역할: 도서관 입구에 있는, 지금 당장 참고할 책들(함수 호출, 지역 변수)을 순서대로 착착 쌓아놓는 작은 서가입니다.
  • 규칙: 가장 마지막에 올려놓은 책을 가장 먼저 꺼냅니다 (LIFO - Last In, First Out).
  • 특징:
    • 매우 빠릅니다. 데이터를 넣고 빼는 위치가 항상 정해져 있어 관리가 단순하고 빠릅니다.
    • 크기가 제한적입니다. 컴파일 타임에 크기가 확정되는 데이터, 즉 Struct, Int, Double 같은 값 타입(Value Type) 데이터가 주로 저장됩니다.
    • 자동으로 정리됩니다. 함수 호출이 끝나거나 해당 코드 블록({ })을 벗어나면, 서가에 쌓였던 책들(데이터)이 자동으로 치워집니다.

🌳 힙(Heap): '자유 열람실'

  • 역할: 도서관의 넓은 부분을 차지하는, 크기도 모양도 제각각인 다양한 책들(클래스 인스턴스)을 보관하는 자유 열람실입니다.
  • 규칙: 빈 공간이 있으면 어디든 책을 둘 수 있습니다. 대신 어디에 뒀는지 '좌석표(메모리 주소)'를 잘 기억하고 있어야 합니다.
  • 특징:
    • 유연하지만 느립니다. 동적으로(프로그램 실행 중에) 필요한 만큼 공간을 할당받으므로 유연하지만, 어디에 공간을 할당할지 찾고 관리하는 과정 때문에 스택보다 느립니다.
    • 공간이 넉넉합니다. Class로 만든 참조 타입(Reference Type) 객체처럼, 프로그램 전반에 걸쳐 사용되어야 하거나 크기가 큰 데이터를 저장합니다.
    • 수동(자동) 관리가 필요합니다. 직접 치우지 않으면 계속 자리를 차지합니다. Swift에서는 ARC(Automatic Reference Counting)가 이 좌석표(참조)를 지켜보다가, 아무도 보고 있지 않은 책(객체)을 알아서 치워주는 역할을 합니다.

✅ 궁금증 해결: 메모리는 이렇게 동작합니다!

이제 스택과 힙을 알았으니, 나머지 궁금증들을 풀어보죠.

1. 객체의 크기는 언제, 어떻게 결정될까?

결론부터 말하면, 컴파일 타임(Compile Time)에 결정됩니다.

컴파일러는 우리가 작성한 코드를 보고 StructClass의 설계도에 어떤 프로퍼티가 있는지 분석하여, 인스턴스 하나를 만들 때 필요한 총 메모리 크기를 미리 계산해 둡니다.

// 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바이트짜리 주소를 가집니다.

2. 함수가 끝나도 Struct가 살아있는 것처럼 보이는 이유

이는 값 타입의 '복사(Copy)' 특성 때문입니다.

함수 내에서 Struct 인스턴스를 생성하면 그 값은 함수의 스택 공간에 저장됩니다. 함수가 이 값을 반환(return)할 때, 값 그 자체가 아닌 '복사본'을 만들어 넘겨줍니다. 원본은 함수 종료와 함께 스택에서 사라지지만, 복사본이 새로운 변수에 할당되었기 때문에 계속 사용할 수 있는 것이죠.


🚀 실전 적용: SwiftUI의 View는 왜 Struct일까?

이 모든 지식을 바탕으로 마지막 질문에 답할 수 있습니다. SwiftUI가 ViewClass가 아닌 Struct로 설계한 데에는 명확한 이유가 있습니다.

  1. ⚡️ 엄청난 속도 (Performance): View는 상태가 바뀔 때마다 계속해서 새로 그려집니다(실제로는 버리고 새로 만듭니다). 스택 기반의 Struct는 생성과 파괴 비용이 거의 0에 가까울 정도로 저렴하기 때문에, 이러한 방식에 완벽하게 부합합니다.

  2. 🔒 예측 가능하고 안전한 상태 (Predictability): View는 값 타입이므로 어딘가에서 나도 모르게 데이터가 변경될 위험(Side-Effect)이 없습니다. View는 오직 주어진 상태(@State 등)에 의해서만 결정됩니다. UI = f(State) 라는 선언적 UI의 핵심 공식이 지켜지는 것이죠.

  3. 💡 값으로서의 UI (UI as a Value): View는 UI 그 자체가 아니라, 특정 상태일 때 UI가 어때야 하는지에 대한 '설명서'이자 '값'입니다. SwiftUI는 이 가벼운 설명서들을 비교(Diffing)하여 실제 화면에서 변경이 필요한 부분만 최소한으로 업데이트합니다.


맺음말

StructClass의 차이는 단순히 '상속이 되냐, 안 되냐'의 문제가 아니었습니다. 메모리가 어떻게 관리되고, 데이터가 어떻게 전달되는지에 대한 근본적인 차이를 이해하는 것이었습니다.

  • 데이터가 작고, 독립적이며, 사본을 만들어 전달하는 것이 더 안전할 때는 Struct.
  • 데이터가 크고, 여러 곳에서 공유되어야 하며, 유일한 상태를 유지해야 할 때는 Class.

이 원칙을 이해하고 상황에 맞는 도구를 선택하는 것이 더 안정적이고 효율적인 Swift 코드를 작성하는 첫걸음이 될 것입니다.

profile
iOS 개발자입니다.

0개의 댓글