전공시간에 Java를 배우며 객체지향 프로그래밍에 익숙해진 나로써, SwiftUI에서 View는 struct로, ViewModel은 class로 구현하는 패턴을 처음 접했을 때 많은 의문이 들었다. Java에서는 대부분의 객체를 class로 구현하는 것이 일반적이었기에, 특히 View를 struct로 구현한다는 점이 더욱 낯설게 느껴졌다.
이러한 의문은 객체지향 프로그래밍에 익숙한 개발자들에게 자연스러운 현상이라고 생각했다. class를 사용한 상속과 다형성이 더 친숙하고, struct는 상대적으로 제한적인 기능을 가지고 있어 보이기 때문이다. 하지만 SwiftUI에서 이러한 구조를 채택한 데에는 분명한 이유가 있다고 생각했다.
View를 struct로 구현하는 것은 성능과 관련이 깊다. struct는 값 타입으로 메모리의 스택 영역에 저장되어 힙 영역을 사용하는 class보다 더 빠른 메모리 접근이 가능하다. SwiftUI는 UI 업데이트가 빈번하게 발생하는 프레임워크이므로, View의 생성과 소멸이 자주 일어난다. struct를 사용하면 이러한 작업이 매우 효율적으로 이루어질 수 있다.
또한 struct의 값 타입 특성은 불변성을 보장하여 SwiftUI의 선언적 UI 패러다임과 잘 어울린다.
반면 ViewModel을 class로 구현하는 것은 데이터 공유와 상태 관리를 위해서다. class는 참조 타입이므로 여러 View에서 동일한 ViewModel 인스턴스를 참조할 수 있다. 이는 앱의 상태를 중앙에서 관리하고 여러 View 간에 데이터를 공유해야 할 때 매우 유용하다.
특히 SwiftUI의 데이터 바인딩 시스템에서 핵심적인 ObservableObject 프로토콜은 class에서만 사용할 수 있도록 설계되어 있어, @Published 속성 래퍼를 통한 자동 UI 업데이트가 가능하다.
또한 class는 ARC를 통한 메모리 관리가 가능하다. 이는 네트워크 요청이나 데이터베이스 작업과 같이 수명이 긴 작업을 처리하는 ViewModel에서 특히 중요하다. 참조 카운팅을 통해 리소스가 더 이상 필요하지 않을 때 자동으로 해제되므로, 메모리 관리가 효율적으로 이루어진다.
// View는 struct로 구현하여 빠른 생성과 소멸이 가능
struct ContentView: View {
// ViewModel은 class로 구현하여 여러 View에서 공유 가능
@StateObject private var viewModel = ContentViewModel()
var body: some View {
VStack {
Text(viewModel.data)
// 다른 View에 동일한 ViewModel 전달
DetailView(viewModel: viewModel)
}
}
}
// 상태 관리를 위한 ViewModel은 class로 구현
class ContentViewModel: ObservableObject {
@Published var data: String = ""
func updateData() {
// 비즈니스 로직 수행
data = "새로운 데이터"
}
}
// 같은 ViewModel을 공유하는 다른 View
struct DetailView: View {
@ObservedObject var viewModel: ContentViewModel
var body: some View {
Text("상세 뷰: \(viewModel.data)")
}
}
이러한 특성들을 이해하고 나면, SwiftUI의 이런 구조가 단순히 규칙이 아닌 각 타입의 장점을 최대한 활용하기 위한 의도적인 설계임을 알 수 있다. View는 가볍고 빠른 struct로, 상태 관리는 안정적이고 공유 가능한 class로 구현함으로써, 현대적이고 효율적인 UI 프레임워크를 만들 수 있게 된 것이다.