[Swift] struct와 class 사용하기

@inup· 4 min read

Swift로 데이터 모델을 설계할 때 struct(구조체)와 class(클래스) 중 무엇을 사용해야 할지는 입문자가 가장 많이 고민하는 주제다. 두 문법은 프로퍼티(변수)와 메서드(함수)를 정의할 수 있다는 점에서 외형상 매우 유사하다. 하지만 메모리를 관리하고 데이터를 전달하는 방식에서 결정적인 차이가 있다.

값 타입(Value Type) vs 참조 타입(Reference Type)

이것이 두 가지를 구분하는 핵심이다. struct값 타입이고, class참조 타입이다.

1. struct: 값 타입

구조체 인스턴스를 변수에 할당하거나 함수의 파라미터로 전달할 때, 값이 복사(Copy)된다. 즉, 새로운 변수는 원본과는 완전히 별개의 데이터를 가지게 된다.

struct Resolution {
    var width = 0
    var height = 0
}

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd // hd의 값이 '복사'되어 cinema에 할당됨

cinema.width = 2048 // cinema의 값을 변경

// 원본 hd는 영향을 받지 않음
print("hd width: \(hd.width)") // 1920
print("cinema width: \(cinema.width)") // 2048

위 예제에서 cinema의 너비를 변경해도 hd의 너비는 그대로 유지된다. 서로 다른 메모리 공간을 점유하고 있기 때문이다.

2. class: 참조 타입

클래스 인스턴스를 변수에 할당하거나 함수에 전달할 때, 참조(Reference, 주소값)가 전달된다. 데이터 자체가 복사되는 것이 아니라, 데이터가 있는 위치를 가리키는 포인터가 전달되는 것이다. 따라서 여러 변수가 하나의 인스턴스를 공유하게 된다.

class VideoMode {
    var resolution = Resolution(width: 0, height: 0)
    var frameRate = 0.0
}

let tenEighty = VideoMode()
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty // 참조(주소)가 할당됨
alsoTenEighty.frameRate = 30.0

// 원본 tenEighty도 영향을 받음 (같은 인스턴스를 바라보기 때문)
print("tenEighty frameRate: \(tenEighty.frameRate)") // 30.0

alsoTenEighty를 수정했는데 tenEighty의 값도 함께 변했다. 이는 두 상수가 메모리 상의 동일한 VideoMode 인스턴스를 가리키고 있기 때문이다.

언제 무엇을 써야 할까?

Objective-C와 같은 과거의 언어에서는 클래스가 객체 지향 프로그래밍의 기본 단위였지만, Swift에서는 struct 사용을 기본으로 권장한다.

  1. struct를 사용하는 경우

    • 연관된 간단한 값의 집합을 캡슐화할 때.
    • 캡슐화된 값들이 참조보다는 복사되는 것이 합리적일 때.
    • 상속이 필요 없을 때.
    • 데이터 모델(Model)의 대부분.
  2. class를 사용하는 경우

    • Objective-C 런타임 기능과의 호환성이 필요할 때.
    • 상속 구조가 반드시 필요할 때.
    • 인스턴스의 정체성을 제어해야 할 때 (값의 동등함이 아니라, 메모리 주소가 같은지 비교하는 === 연산이 필요할 때)

이번 스위프트를 이용한 macOS 응용 프로그램인 Claudemeter에서도 사용량 데이터와 같은 핵심 모델은 대부분 struct로 구현했다. 멀티 스레드 환경이나 복잡한 비동기 로직에서 데이터가 의도치 않게 공유되어 변경되는 부작용을 원천적으로 막을 수 있었기 때문이다. 데이터의 불변성과 독립성을 보장하는 것이 유지보수에 훨씬 유리하다는 것을 경험했다.

@inup
언제나 감사합니다 👨‍💻