Massive View에서 MVVM으로의 전환
낯선 언어를 매일 학습하며 몰입과 성장을 기록하는 일지
· 2 min read
MVVM #
문제상황 #
- 처음 해보는 Swift 구조 관리에 어려움 발생
- 화면을 그리는 코드와 로직을 담당하는 코드가 동시에 혼재해 있어서 디자인 변경 시 로직까지 건드리는 상황 발생
- 해결책으로 MVVM 도입 결정
ViewModel #
- LotttoViewModel 생성
- Input, Processing, Output의 파이프라인을 가짐
Input #
func addTicketFromQR(url: String) {
print("📷 스캔 감지: \(url)")
let result = parseQRUrl(url)
let newTickets = result.tickets
let scannedRound = result.round
if newTickets.isEmpty {
print("⚠️ 유효하지 않은 로또 QR입니다.")
return
}
scannedTickets.insert(contentsOf: newTickets.reversed(), at: 0)
if let round = scannedRound {
print("🔄 \(round)회차 감지! 당첨 정보 로딩 시작")
Task {
await fetchWinningData(round: round)
}
}
}
- View에서 발생한 QR코드 스캔을 받아, 내부적으로 비즨스 로직을 수행
Processing #
func checkRank(for ticket: LottoTicket) -> LottoRank {
guard !winningNumbers.isEmpty else { return .miss }
let count = matchCount(for: ticket)
let hasBonus = ticket.numbers.contains(bonusNumber)
if count == 5 {
return hasBonus ? .second : .third
}
let ranks: [LottoRank] = [.miss, .miss, .miss, .fifth, .fourth, .miss, .first]
return count < ranks.count ? ranks[count] : .miss
}
- View로부터 받아온 입력을 통해 등수를 계산
- 기존에는 View에서 모든 로직들을 직접 수행하였으나 View는 입출력만 하고 비즈니스 로직은 하지 않도록 변경
- 즉 View를 Dumb하게 만듬
Output #
@Published var scannedTickets: [LottoTicket] = []
@Published var winningNumbers: [Int] = []
@Published var bonusNumber: Int = 0
@Published var showResults = false
@Published var rankPrizes: [LottoRank: Int] = [:]
@Published var isLoading = false
@Published var errorMessage: String?
@Published var fetchedWinningNumbers: [Int] = []
@Published를 통해 해당 ViewModel을 보고 있는 모든 View에게 변경사항을 알리도록 함
Data Flow #
ContentView #
- 앱의 진입점이므로 ViewModel을 생성하고 소유해야 함
- @StateObject 사용하여 ViewModel 선언
struct ContentView: View {
@StateObject private var viewModel = LottoViewModel()
...
}
TicketListView #
- 부모가 만든 ViewModel을 전달받아 사용만 함
- @ObservedOBject를 사용하여 ViewModel 선언
- TicketListView에도 @StateObject를 사용했다면 뷰가 재생성될 때마다 뷰모델이 초기화되는 일이 발생
struct TicketListView: View {
@ObservedObject var viewModel: LottoViewModel
...
}
Model #
- 데이터 그 자체를 의미하며, UI와 무관한 역할
- LottoRank, LottoResult, LottoTicket
- LottoRank의 경우 Model 내부에서 색상, 상금 등을 Dictionary를 통해 설계하여 응집도를 높임
- OCP를 준수하려 노력
결과 #
- UI 디자인 변경 시 로직 코드와 분리되어 있어 안전한 변경이 가능
- ConfettiView와 같은 새로운 기능 추가 시 기존 코드를 수정하지 않고 쉽게 확장 가능
- MVVM을 적용하여 책임의 분리를 통해 안정성을 높일 수 있었음