SwiftUI 고급 상태 관리 기법: @EnvironmentObject와 Combine

작성일 :

SwiftUI 고급 상태 관리 기법: @EnvironmentObject와 Combine

SwiftUI는 애플리케이션의 UI를 선언적으로 작성할 수 있게 해주는 강력한 프레임워크입니다. 간단한 애플리케이션에서는 @State와 @Binding 정도로도 상태 관리를 충분히 할 수 있지만, 복잡한 애플리케이션에서는 좀 더 강력한 상태 관리 기법이 필요합니다. 여기서 @EnvironmentObjectCombine 프레임워크가 중요한 역할을 합니다. 이 글에서는 @EnvironmentObjectCombine을 활용하여 애플리케이션의 상태를 효과적으로 관리하는 방법을 설명합니다.

@EnvironmentObject 소개

@EnvironmentObject는 여러 뷰 사이에서 공유되는 객체를 쉽게 사용하게 도와줍니다. 이 객체는 앱의 환경에 묶여있으며, 여러 하위 뷰에서 이를 참조할 수 있습니다. 상위 뷰에서 @EnvironmentObject로 지정된 객체는 하위 뷰에서 자동으로 주입됩니다.

먼저, 환경 객체로 사용할 클래스는 ObservableObject 프로토콜을 준수해야 합니다. 이 클래스는 SwiftUI에서 상태 변화를 감지할 수 있게 해줍니다.

swift
import SwiftUI
import Combine

class AppState: ObservableObject {
    @Published var count: Int = 0
}

@Published를 사용하면 해당 속성의 값이 변경될 때마다 자동으로 트리거됩니다. 이제 이 객체를 SwiftUI 뷰에서 어떻게 사용하는지 살펴보겠습니다.

swift
struct CounterView: View {
    @EnvironmentObject var appState: AppState
    var body: some View {
        VStack {
            Text("Count: \(appState.count)")
            Button(action: {
                appState.count += 1
            }) {
                Text("Increment")
            }
        }
    }
}

여기서 CounterViewAppState@EnvironmentObject로 받습니다. 이제 이 뷰를 렌더링 할 때, AppState 객체를 환경에 주입해야 합니다.

swift
@main
struct MyApp: App {
    @StateObject var appState = AppState()
    var body: some Scene {
        WindowGroup {
            CounterView().environmentObject(appState)
        }
    }
}

MyApp에서 @StateObjectAppState 객체를 생성하고, 이를 환경 객체로 주입합니다. 이제 CounterView에서 이 상태를 자동으로 사용할 수 있게 됩니다.

Combine 소개

Combine은 애플리케이션의 반응형 프로그래밍을 위한 강력한 프레임워크입니다. 데이터 스트림을 생성하고 이를 관찰하고 변형할 수 있습니다. Combine을 활용하면 비동기 작업을 좀 더 쉽게 관리할 수 있습니다. Combine은 여러번의 연산 체인을 통해 데이터의 변화를 감지하고 처리할 수 있게 도와줍니다.

다음은 Combine을 사용한 간단한 예제입니다.

swift
import Combine

class DataFetcher {
    var cancellable: AnyCancellable?
    func fetchData() {
        let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
        cancellable = URLSession.shared.dataTaskPublisher(for: url)
            .map { $0.data }
            .decode(type: Todo.self, decoder: JSONDecoder())
            .sink(receiveCompletion: { completion in
                switch completion {
                case .finished:
                    print("Done")
                case .failure(let error):
                    print("Error: \(error)")
                }
            }, receiveValue: { todo in
                print("Todo: \(todo)")
            })
    }
}

위 코드에서 URLSession.shared.dataTaskPublisher를 사용하여 URL 요청을 수행하고, 받은 데이터를 Todo 구조체로 디코딩합니다. sink를 사용하여 최종 결과를 수신하고, 성공 또는 실패를 처리합니다.

@EnvironmentObject와 Combine 결합

이제 @EnvironmentObjectCombine을 결합하여 조금 더 복잡한 애플리케이션 상태 관리를 구현해보겠습니다. 예를 들어, 원격 서버에서 데이터를 받아와서 앱 상태에 저장하는 애플리케이션을 만들어봅시다.

먼저 AppState 클래스에 Combine을 사용하여 원격 데이터를 가져오는 메소드를 추가합니다.

swift
import SwiftUI
import Combine

class AppState: ObservableObject {
    @Published var todos: [Todo] = []
    private var cancellable: AnyCancellable?

    func fetchTodos() {
        let url = URL(string: "https://jsonplaceholder.typicode.com/todos")!
        cancellable = URLSession.shared.dataTaskPublisher(for: url)
            .map { $0.data }
            .decode(type: [Todo].self, decoder: JSONDecoder())
            .replaceError(with: [])
            .receive(on: DispatchQueue.main)
            .assign(to: \.$todos, on: self)
    }
}

이제 SwiftUI 뷰에서 이 메소드를 호출하여 데이터를 가져올 수 있습니다.

swift
struct TodoListView: View {
    @EnvironmentObject var appState: AppState

    var body: some View {
        List(appState.todos) { todo in
            Text(todo.title)
        }
        .onAppear {
            appState.fetchTodos()
        }
    }
}

TodoListView@EnvironmentObjectAppState를 받아와서 todos를 리스트로 표시합니다. onAppear에서 fetchTodos 메소드를 호출하여 데이터를 가져옵니다.

마지막으로, 앱의 진입점을 설정합니다.

swift
@main
struct MyApp: App {
    @StateObject var appState = AppState()

    var body: some Scene {
        WindowGroup {
            TodoListView().environmentObject(appState)
        }
    }
}

이와 같이 @EnvironmentObjectCombine을 결합하여 복잡한 상태 관리를 보다 쉽게 할 수 있습니다. 이를 통해 확장성과 유지보수성을 높인 애플리케이션을 개발할 수 있습니다.