Class는 Swift 언어의 참조 타입(reference type)으로, 인스턴스가 할당 시 복사되지 않고 참조로 전달됩니다. 구조체와 달리 클래스는 상속, 초기화 해제 및 메모리 관리를 위한 자동 참조 카운팅 ARC를 지원합니다. Swift Programming Language Guide, 2026에 따르면, Apple의 UI 프레임워크(UIKit, AppKit)로 작업하고 객체의 공유 정체성이 필요한 패턴을 구현하려면 class가 필요합니다. class와 struct 사이의 선택은 Swift의 주요 아키텍처 결정 중 하나입니다.
핵심 포인트
Class — 속성과 메서드를 상속 및 동적 디스패치 지원과 함께 단일 엔터티로 그룹화하는 Swift의 복합 참조 타입입니다. struct와 달리 클래스 인스턴스는 힙에 생성되며, 변수는 데이터 자체가 아니라 이 인스턴스에 대한 참조를 저장합니다.
클래스 변수를 다른 변수에 할당하면 둘 다 메모리에서 동일한 객체를 참조합니다. 한 참조를 통한 변경 사항은 다른 참조를 통해 표시됩니다 — 이는 참조 타입의 기본 속성이며, 위임, 관찰 및 공유 상태 패턴에서 사용됩니다.
Apple Swift 문서에 따르면, 모든 UI 구성 요소가 UIView와 UIViewController에서 상속되는 UIKit 및 AppKit으로 작업하는 유일한 방법은 클래스입니다. 또한 객체 정체성(하나의 객체에 대한 두 개의 참조)과 제어된 수명이 필요한 패턴을 구현하려면 클래스가 필요합니다.
참조 타입 의미론 — 클래스의 주요 속성입니다. 클래스를 새 변수에 할당할 때 Swift는 데이터가 아니라 참조를 복사합니다. 동일한 인스턴스를 참조하는 모든 변수는 현재 상태를 보고 수정할 수 있습니다. 이 동작은 각 변수가 독립적인 복사본을 얻는 값 타입과 근본적으로 다릅니다.
// 참조 타입 의미론 예제
class User {
var name: String
init(name: String) { self.name = name }
}
let user1 = User(name: "Alice")
let user2 = user1 // user2 — 동일한 참조
user2.name = "Bob"
print(user1.name) // "Bob" — user2를 통해 변경됨!
// === 연산자로 정체성 확인
print(user1 === user2) // true — 동일한 객체
===(정체성) 및 !== 연산자는 두 변수가 동일한 클래스 인스턴스를 참조하는지 확인합니다. 이는 속성 값을 비교하는 ==(동등성)과 다릅니다. === 연산자는 struct에 사용할 수 없습니다 — 값 타입에는 정체성이 없습니다.
상속 — 클래스가 부모 클래스의 속성과 메서드를 채택할 수 있는 메커니즘입니다. Swift에서 클래스는 하나의 부모로부터만 상속받을 수 있지만(단일 상속), 여러 프로토콜을 구현할 수 있습니다. override 키워드를 사용하면 상속된 메서드나 속성을 재정의할 수 있습니다.
다른 클래스에서 상속받지 않는 모든 클래스는 자동으로 기본 클래스가 됩니다(NSObject와 혼동하지 마세요). 하위 클래스는 이름 뒤에 콜론을 붙여 부모를 지정합니다. 하위 클래스가 부모의 메서드를 재정의하는 경우 부모의 동작을 유지하기 위해 super.method()를 호출해야 합니다 — 이는 컴파일러 요구 사항입니다.
class 앞의 키워드 final은 상속을 방지합니다. 컴파일러는 정적 디스패치를 통해 final 클래스의 메서드 호출을 최적화하여 성능을 향상시킬 수 있습니다. 확장할 의도가 없는 클래스에는 final을 사용하세요 — 의도를 문서화하고 코드를 빠르게 합니다.
// Swift에서 클래스 상속 예제
class Vehicle {
var speed: Double = 0
func description() -> String {
"Speed: \(speed) km/h"
}
}
// Car는 Vehicle을 상속받음
class Car: Vehicle {
var brand: String = "Unknown"
override func description() -> String {
"\(brand) - \(speed) km/h"
}
}
// Final 클래스 — 상속 방지
final class ElectricCar: Car {
var batteryLevel: Double = 100
}
let tesla = ElectricCar()
tesla.brand = "Tesla"
tesla.speed = 120
print(tesla.description()) // "Tesla - 120.0 km/h"
상속은 강력하지만 책임이 따르는 메커니즘입니다. 깊은 계층 구조(5레벨 이상)는 유지 관리와 테스트를 복잡하게 만듭니다. 상속 없이 기능을 재사용하려면 확장 및 프로토콜 지향 프로그래밍과 함께 프로토콜을 사용하세요 — Apple이 깊은 클래스 계층 구조의 대안으로 권장하는 접근 방식입니다.
Swift는 클래스 메모리 관리를 위해 ARC(자동 참조 카운팅)를 사용합니다. 각 클래스 인스턴스에는 강한 참조 카운터가 있습니다. 새로운 강한 참조가 생성되면 카운터가 증가하고, 소멸되면 감소합니다. 카운터가 0에 도달하면 메모리가 해제됩니다.
Deinit — 클래스 인스턴스가 해제되기 전에 자동으로 호출되는 메서드입니다. 리소스를 해제합니다: 파일을 닫고, 알림 구독을 취소하고, 타이머를 중지합니다. Deinit은 클래스에서만 사용할 수 있습니다 — 구조체와 열거형에는 없습니다.
강한 참조 순환(retain cycles)을 방지하기 위해 Swift는 weak 및 unowned 참조를 제공합니다. Weak는 객체가 해제될 때 자동으로 nil이 되는 옵셔널 참조입니다. Unowned는 옵셔널이 아니지만 현재 컨텍스트보다 오래 살 것이 보장된 객체를 참조합니다. 일반적인 retain cycle은 부모-자식 관계에서 발생합니다: 자식이 부모에 대한 강한 참조를 보유합니다.
// ARC 및 약한 참조 예제
class Parent {
var name: String
var child: Child?
init(name: String) { self.name = name }
deinit { print("\(name) 해제됨") }
}
class Child {
var name: String
weak var parent: Parent? // weak가 retain cycle을 방지함
init(name: String) { self.name = name }
deinit { print("\(name) 해제됨") }
}
var parent: Parent? = Parent(name: "Anna")
parent?.child = Child(name: "Mia")
parent?.child?.parent = parent
parent = nil // 두 객체 모두 해제됨!
// weak가 없으면 retain cycle이 생성됨
자식 객체에서 부모로의 참조와 클로저의 캡처 목록에는 항상 weak를 사용하세요. Unowned는 객체가 현재 컨텍스트보다 오래 살 것이 확실한 경우에만 사용하세요 — unowned의 잘못된 사용은 해제된 메모리에 접근할 때 충돌을 일으킬 수 있습니다.
class와 struct 사이의 선택은 성능, 스레드 안전성 및 API 설계에 영향을 미치는 아키텍처 결정입니다. 각 경우에 올바른 결정을 내리는 데 도움이 되도록 주요 차이점 표를 고려하세요.
| 특성 | class | struct |
|---|---|---|
| 타입 | 참조 타입 | 값 타입(복사) |
| 메모리 | 힙 + ARC | 스택 / 인라인 |
| 상속 | 지원(단일 부모) | 지원 안 함 |
| Deinit | 있음 | 없음 |
| 정체성 (===) | 지원 | 지원 안 함 |
| Mutating | 필요 없음(항상 mutating) | mutating에서만 |
| Memberwise init | 생성되지 않음 | 자동 생성 |
| 스레드 안전성 | 보장되지 않음(공유 상태) | 보장됨(복사) |
공유 정체성(코드의 여러 부분이 하나의 객체로 작업), 상속 또는 Objective-C 런타임과의 상호 작용이 필요할 때 클래스를 사용하세요. 그 외 모든 경우에는 구조체가 선호됩니다 — 더 빠르고, 멀티스레드 코드에서 안전하며, 메모리 관리가 필요하지 않습니다.
Apple이 기본적으로 struct를 사용할 것을 권장함에도 불구하고, 여러 특정 시나리오에서는 클래스가 필요합니다. 실제 예제와 함께 각각을 살펴보겠습니다.
iOS 및 macOS의 모든 UI 구성 요소는 UIView(iOS) 또는 NSView(macOS)에서 상속받는 클래스입니다. UIViewController를 구조체로 대체할 수 없습니다 — 리소스를 해제하기 위해 상속과 deinit이 필요합니다. UIKit으로 작업할 때는 컨트롤러, 뷰 및 해당 델리게이트에 클래스를 사용하세요.
싱글톤 패턴(전체 응용 프로그램에 대한 하나의 클래스 인스턴스)은 참조 의미론이 필요합니다. 관리자: NetworkManager, SettingsManager, AnalyticsService — 일반적으로 공유 정적 속성이 있는 클래스로 구현됩니다. 구조체는 각 복사본이 독립적인 인스턴스가 되므로 적합하지 않습니다.
객체가 유일한 진실 공급원이어야 하고 모듈 간에 참조로 전달되어야 하는 경우 — class를 사용하세요. 이는 한 곳에서 객체를 변경하는 것이 모든 종속 구성 요소에 표시되어야 하는 상태 관리에 적용됩니다. SwiftUI의 ObservableObject의 경우 클래스가 필수입니다.
// ObservableObject — SwiftUI에 class 필요
import SwiftUI
class AppViewModel: ObservableObject {
@Published var isLoggedIn: Bool = false
@Published var username: String = ""
func login(user: String) {
isLoggedIn = true
username = user
}
}
// SwiftUI View에서 사용
struct ContentView: View {
@StateObject var viewModel = AppViewModel()
var body: some View {
Text(viewModel.isLoggedIn ? "환영합니다" : "로그인")
}
}
규칙: 객체에 정체성(두 개의 참조 -> 하나의 객체)이 있어야 하거나, 단일 인스턴스이거나, UIKit/Objective-C로 작업해야 하는 경우 — class를 선택하세요. 객체가 단순히 데이터를 포함하는 경우 — struct를 선택하세요.
자주 묻는 질문
Class는 상속, 초기화 해제 및 메모리 관리를 위한 ARC를 지원하는 Swift의 참조 타입입니다. 클래스 인스턴스는 힙에 저장되며 변수는 객체의 복사본이 아닌 참조를 포함합니다.
Class — 참조 타입(참조로 전달됨), 상속 및 deinit을 지원합니다. Struct — 값 타입(복사됨), 상속을 지원하지 않지만 프로토콜을 구현하고 memberwise init을 자동으로 받습니다. Swift는 기본 타입으로 struct를 권장합니다.
ARC(자동 참조 카운팅) — Swift 클래스의 메모리 관리 메커니즘입니다. 각 인스턴스에는 강한 참조 카운터가 있습니다. 카운터가 0에 도달하면 메모리가 해제됩니다. weak 및 unowned 참조는 객체 간의 retain cycles을 방지합니다.
Deinit — 메모리가 해제되기 전에 자동으로 호출되는 클래스 메서드입니다. 파일 닫기, 알림 구독 취소 및 기타 정리 작업에 사용됩니다. Deinit은 클래스에서만 사용할 수 있으며 구조체에는 없습니다.
UIKit/AppKit UI 구성 요소, 싱글톤, SwiftUI의 ObservableObject, 공유 정체성이 있는 객체(델리게이트, 옵저버) 및 Objective-C 런타임으로 작업할 때 class를 사용하세요. 데이터 모델, DTO 및 구성에는 struct가 선호됩니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.