[SwiftUI,RxSwift] 데이터 모델링, 메인 UI 그리기 , DisposeBag , onNext , onAppear

SwiftUI 를 사용해서 개인 프로젝트를 시작하기 위해 기초적인 작업을 맞춰놨다.

 

일단 기본적으로 MVVM에서 데이터를 전달하는 방식과 RxSwift 를 활용하는 내용을 실전에 활용해 보기 위해 한번 테스트를 해봤다고 생각 하면서 진행했다.

 

Model 및 ViewModel 생성

struct Workout: Identifiable {
    var id = UUID()
    var name: String
    var date: Date
    var duration: TimeInterval
    var caloriesBurned: Double
}

운동 기록을 하기 위해 필요한 데이터를 struct로 생성하였다.

import Foundation
import RxSwift
import RxCocoa

class WorkoutViewModel: ObservableObject {
    let loadWorkouts = PublishSubject<Void>()
    
    @Published var workouts: [Workout] = []
    @Published var errorMessage: String?
    
    private let disposeBag = DisposeBag()
    
    init() {
    let dummyWorkouts = [
        Workout(name: "Running", date: Date(), duration: 3600, caloriesBurned: 300),
        Workout(name: "Swimming", date: Date(), duration: 1800, caloriesBurned: 250),
        Workout(name: "Cycling", date: Date(), duration: 5400, caloriesBurned: 500)
    ]
        
    loadWorkouts
            .map { dummyWorkouts }
            .subscribe(onNext: { [weak self] workouts in
                self?.workouts = workouts
            }, onError: { [weak self] error in
                self?.errorMessage = error.localizedDescription
            })
            .disposed(by: disposeBag)
    }
}

그 다음 더미 데이터를 만들어 데이터를 전달하고 DisposeBag을 활용해 자동으로 해제될 수 있도록 구현했다.

 

loadWorkouts의 값을 입력받아 더미데이터가 들어있는 모델링을 불러오는 방식이다.

 

ObservableObject의 프로토콜을 채택해 SwiftUI와 바인딩 될 수 있도록 설정하고 @Published 프로퍼티 래퍼를 사용하여 workouts와 errorMesseage를 선언한다.

 

이렇게 설정하면 값이 변경될때 SwiftUI 뷰에 자동으로 업데이트 될 수 있다.

 

DisposeBag

위에서 언급된 디스포스백을 설명하자면 RxSwift에서 메모리 관리를 도와주는 중요한 도구이다.

 

RxSwift를 사용하여 생성된 옵저버블 시퀀스들은 무한히 지속될 수 있습니다. 이러한 시퀀스들이 제대로 해제되지 않으면 메모리 누수가 발생할 수 있다.

 

disposeBag은 이러한 시퀀스들을 한 곳에 모아 두고, DisposeBag이 할당 해제될 때 자동으로 시퀀스들도 함께 해제되도록 하는 명령어이다.

 

DisposeBag 의 역할

  • 메모리 누수 방지 : DisposeBag은 구독을 수집하고 할당 해제될 때 이 구독들도 해제되어 메모리 누수를 방지한다.
  • 코드 가독성 향상 : 여러 구독들을 한 곳에 관리하게 되기 때문에 코드가 더 깔끔해지고 가독성이 높아진다.
  • 명시적 해제 : 수동으로 구독을 해제하지 않아도 DisposeBag 을 사용하여 구독을 자동으로 해제할 수 있다.

onNext

onNext는 RxSwift에서 사용되는 연산자로 옵저버블에 새로운 이벤트를 방출하는데 사용된다.

 

View의 구성

import SwiftUI

struct WorkoutsView: View {
    @ObservedObject var viewModel = WorkoutViewModel()
    
    var body: some View {
        NavigationView {
            List(viewModel.workouts) { workout in
                HStack {
                    VStack(alignment: .leading) {
                        Text(workout.name)
                            .font(.headline)
                        Text("\\(formatDuration(workout.duration)) - \\(workout.caloriesBurned) kcal")
                    }
                    Spacer()
                    Image(systemName: "checkmark.seal.fill")
                }
            }
            .navigationTitle("Workouts")
            .onAppear {
                viewModel.loadWorkouts.onNext(())
            }
            .alert(item: $viewModel.errorMessage) { message in
                Alert(title: Text("Error"), message: Text(message), dismissButton: .default(Text("OK")))
            }
        }
    }
    
    private func formatDuration(_ duration: TimeInterval) -> String {
        let hours = Int(duration) / 3600
        let minutes = (Int(duration) % 3600) / 60
        return String(format: "%02d:%02d", hours, minutes)
    }
}

extension String: Identifiable {
    public var id: String { self }
}

그 다음 View 내 SwiftUI 를 활용해 컴포넌트들을 호출하고 모델에서 사용된 데이터를 넣어주면 쉽게 데이터 연결이 가능하다.

SwiftUI를 써보면서 느끼는 거지만 확실히 UIKit 보다 컴포넌트 추가가 수월하다..

 

onAppear

onAppear는 SwiftUI에서 뷰가 화면에 나타날 때 실행되는 코드를 지정하는 뷰 수정자 이다.

SwiftUI의 뷰 라이프 사이클 중에서 뷰가 처음 그려지거나 다시 나타날때 호출되게 된다.