반응형

 iOS 개발자가 많이 하는 실수 - struct vs class 차이를 오해하여 발생하는 문제 (Value Type / Reference Type 혼동)

 

1. 핵심 차이 요약

구분 struct class
타입 값 타입(Value Type) 참조 타입(Reference Type)
동작 복사(Copy) 참조(Reference)
메모리 스택 중심(COW 포함) 힙 할당
ARC 없음 있음
Thread-safe 상대적으로 안전 공유 시 위험
SwiftUI/TCA struct 기반 설계 추천 최소화 필요
UIKit View struct 불가 class 필수

핵심 문장:

struct는 복사되고, class는 공유된다.

이 한 문장이 대부분의 문제를 설명한다.


2. 초보자가 흔히 하는 실수

실수 1) struct를 class처럼 생각함

struct User {
    var name: String
}

var a = User(name: "Jun")
var b = a   // 복사

b.name = "Lee"

print(a.name) // "Jun"
print(b.name) // "Lee"

초보자는 a와 b가 같은 객체라고 생각하지만

struct는 값 타입이므로 완전히 다른 인스턴스이다.

→ 값이 공유된다고 착각하여 버그 발생



실수 2) class를 struct처럼 생각함

class User {
    var name: String
    init(name: String) { self.name = name }
}

var a = User(name: "Jun")
var b = a   // 참조 복사 (실제로는 같은 객체)

b.name = "Lee"

print(a.name) // "Lee"
print(b.name) // "Lee"

여기서는 a와 b가 같은 객체이다.

즉, b를 수정하면 a도 수정됨.

→ “왜 값이 바뀌지?” 라는 상황 발생



실수 3) SwiftUI에서 class 기반 ViewModel을 잘못 다룸

class MyViewModel: ObservableObject {
    @Published var count = 0
}

View 쪽에서:

@ObservedObject var vm = MyViewModel() // ❌ 매 렌더링 때마다 새로 만들어짐

→ 상태가 계속 초기화됨

→ SwiftUI가 struct 기반 렌더링이라는 점을 이해하지 못한 실수

정해진 정답:

@StateObject var vm = MyViewModel() // ✔️ 한번만 유지됨


실수 4) TCA에서 State를 class로 선언

struct Feature {
    class State { }   // ❌ 절대 안 됨 (불변성 깨짐)
}

TCA(State, Action, Reducer)에서는

State는 반드시 struct 여야 함.

→ class를 State로 쓰면 불변성(Inmutability) 붕괴 + 예측 불가 상태 변경 발생

→ DebuggingTools(Logging, Time Travel Debugger)가 오작동함



실수 5) struct 내부에 mutable class를 넣어놓는 실수

struct UserState {
    var profile: Profile // Profile이 class라면?
}

외형적으로는 값 타입처럼 보여도

내부에 참조 타입(class)이 있으면 부분적으로 reference 타입처럼 동작해 버림.

이로 인해:

  • struct 복사해도 내부 class는 공유됨
  • 예상치 못한 상태 공유 발생
  • SwiftUI 렌더링이 꼬임

→ “겉만 struct”인 잘못된 설계


3. Value Type(값 타입)의 장점

Swift가 struct를 적극 추천하는 이유는 다음과 같다.

1) COW(Copy-on-Write)로 설계되어 효율적

값이 실제로 변경되기 전까지는 복사하지 않아 성능에 유리.

2) 상태(State)가 예측 가능

참조 공유가 없어서 디버깅이 쉬움.

3) SwiftUI/TCA 등 최신 프레임워크와 궁합 최상

  • SwiftUI → 모든 View가 struct
  • TCA → State struct 기반

4) Race Condition 감소

참조 타입처럼 여러 스레드에서 같은 객체를 공유하지 않기 때문.


4. Reference Type(class)의 장점과 반드시 필요한 경우

class는 “나쁘다”가 아니라 필요한 곳이 다르다.

✔ 반드시 class를 써야 하는 경우

  1. UIKit View (UIButton, UIView 등)
  2. NSObject 기반 API 사용할 때
  3. 싱글턴(Service, Manager 등)
  4. Actor/GCD 관리 객체
  5. 참조 공유가 필요할 때

즉,

  • UI 요소
  • OS 시스템 객체
  • 서비스/매니저 계층
    에서는 class가 필수다.

5. struct vs class 선택 기준 (실무용 체크리스트)

struct가 맞는 경우

  • 모델(Model)
  • DTO, Domain State
  • 값의 복제가 자연스러운 경우
  • 상태를 명확하게 추적하고 싶을 때
  • SwiftUI/TCA state
  • 테스트 용이성 높이기

class가 맞는 경우

  • UIKit View / ViewController
  • 공유 리소스
  • 싱글턴
  • FileManager / URLSession / CLManager / AVFoundation 등 시스템 객체
  • Objective‑C 기반 API

6. struct vs class를 잘못 선택했을 때 발생하는 문제

  1. 예상치 못한 값 수정
  2. UI 업데이트 이상 동작
  3. SwiftUI View 재렌더링 꼬임
  4. 레이스 컨디션 / 상태 충돌
  5. TCA State 시간 여행 디버깅 불가능
  6. 메모리 관리 어려움(ARC over-retain)

대부분은 참조 타입의 공유로 인해 일어난다.


7. 실무 예시: struct → class 오해로 생긴 버그

struct Counter {
    var count: Int
}

class Manager {
    var counter = Counter(count: 0)
}

let m = Manager()
let c = m.counter
c.count += 1

print(m.counter.count) // 0 ❌ 예상과 다름

초보자는 c.count += 1 하면 Manager 안의 값도 증가한다고 착각하지만

실제로는 Counter가 값 타입이라 복사본만 증가한다.


8. 정리

  • struct = Value Type → 복사
  • class = Reference Type → 공유
  • Swift에서 struct 중심 설계를 선호하는 이유:
    • 안정성, 성능, 예측 가능한 상태
  • 적절한 타입 선택이 코드의 안정성과 예측 가능성을 결정한다.
  • SwiftUI, TCA 등 현대 iOS 아키텍처에서는 struct가 기본이다.
반응형
Posted by 까칠코더
,