728x90
아키텍처
Toucher 개발을 리펙토링 하면서 제일 먼저 한 고민은 "어떤 아키텍처를 해야하는 것인가,,?" 이었습니다.
일단 저희한테 맞는 아키텍처를 찾으려면 어떠한 아키텍처가 SwiftUI 에서 사용이 되는지 알아봤어야 했기에 간단하게 서치를 해봤습니다.
MVC
Model-View-Controller
주로 UIkit에서 이용을 합니다.
SwiftUI에서는 아래와 같은 이유로 사용을 지양하고 있습니다.
- SwiftUI는 선언적인 구문을 사용하여 UI를 정의합니다. 원하는 결과를 선언하고 프레임워크가 알아서 상태 및 레이아웃을 관리합니다.
- SwiftUI는 바인딩(Binding) 및 상태 속성(State Property)과 같은 반응형 프로그래밍 개념을 도입하여 UI 요소를 데이터에 쉽게 바인딩할 수 있습니다.
- MVVM은 단일 소스 오브 트루스 원칙을 강조합니다. 즉, 데이터의 상태는 하나의 ViewModel에 집중되며, 이 상태에 따라 UI가 업데이트됩니다.
Model (모델):
- 데이터와 데이터의 처리를 담당합니다.
- 애플리케이션의 비즈니스 로직, 데이터 관리, 상태 정보 등을 처리합니다.
class CalculatorModel {
var result: Double = 0.0
func add(_ number: Double) {
result += number
}
func subtract(_ number: Double) {
result -= number
}
// 다른 계산 메서드들...
}
View (뷰):
- 사용자 인터페이스를 담당합니다.
- 모델에서 전달된 데이터를 표시하고, 사용자 입력을 받아 컨트롤러에 전달합니다.
import UIKit
class CalculatorView: UIView {
let resultLabel = UILabel()
// 뷰 구성 및 레이아웃 설정 등의 메서드들...
}
Controller (컨트롤러):
- 모델과 뷰를 연결하고 제어합니다.
- 사용자 입력을 받아 모델에 전달하고, 모델의 변경사항을 뷰에 반영합니다.
class CalculatorController {
let calculatorModel = CalculatorModel()
let calculatorView = CalculatorView()
init() {
setupView()
setupActions()
}
private func setupView() {
// 뷰 초기화 및 설정
}
private func setupActions() {
calculatorView.addButton.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
calculatorView.subtractButton.addTarget(self, action: #selector(subtractButtonTapped), for: .touchUpInside)
}
@objc private func addButtonTapped() {
let inputNumber = // 사용자로부터 입력받은 숫자
calculatorModel.add(inputNumber)
updateView()
}
@objc private func subtractButtonTapped() {
let inputNumber = // 사용자로부터 입력받은 숫자
calculatorModel.subtract(inputNumber)
updateView()
}
private func updateView() {
calculatorView.resultLabel.text = "\\(calculatorModel.result)"
}
}
참고 자료
MVVM
Model-View-ViewModel
MVVM은 SwiftUI의 특징과 잘 어울려 UI 코드를 간결하게 유지하면서도 코드의 유지보수성과 확장성을 높일 수 있습니다. 바인딩, 그리고 UI 상태의 효과적인 관리와 업데이트에 있습니다.
Model (모델):
- 데이터와 비즈니스 로직을 담당합니다.
swiftCopy code
struct Task {
var title: String
var isCompleted: Bool
}
class TaskManager {
var tasks: [Task] = []
func addTask(title: String) {
let newTask = Task(title: title, isCompleted: false)
tasks.append(newTask)
}
// 다른 작업 관련 메서드들...
}
ViewModel (뷰모델):
- 뷰에서 사용되는 데이터와 해당 데이터를 처리하는 로직을 담당합니다.
swiftCopy code
import SwiftUI
class TaskListViewModel: ObservableObject {
@Published var tasks: [Task] = []
private var taskManager = TaskManager()
init() {
// 뷰모델 초기화 및 기본 데이터 설정
self.tasks = taskManager.tasks
}
func addTask(title: String) {
taskManager.addTask(title: title)
tasks = taskManager.tasks
}
// 다른 뷰모델 메서드들...
}
View (뷰):
- 사용자 인터페이스를 나타내며, 뷰모델과 바인딩하여 데이터의 상태를 반영합니다.
swiftCopy code
import SwiftUI
struct TaskListView: View {
@ObservedObject var viewModel: TaskListViewModel
@State private var newTaskTitle = ""
var body: some View {
VStack {
TextField("New Task", text: $newTaskTitle)
.padding()
Button("Add Task") {
viewModel.addTask(title: newTaskTitle)
newTaskTitle = ""
}
List(viewModel.tasks) { task in
Text(task.title)
.foregroundColor(task.isCompleted ? .green : .black)
}
}
.padding()
}
}
참고 자료
https://www.youtube.com/watch?v=uQtM6StTsQg
TCA
- View 단독으로 ViewModel의 역할 가능 기존 MVVM의 ViewModel은 데이터 바인딩을 위해 필요하지만 SwiftUI는 View 자체적으로 데이터 바인딩이 가능한 PropertyWrapper를 지원하기에 ViewModel이 더 이상 필요 없다는 의견으로 아래 코드를 비교해 보면 View에서 간단하게 처리 가능한 내용을 ViewModel을 이용할 때 복잡해지는 예를 확인할 수 있습니다.
- SwiftUI와 데이터 흐름의 충돌 선언형 언어인 SwiftUI에서 데이터 흐름은 단방향을 지향하는데 ViewModel은 데이터의 흐름을 양방향으로 만들고 복잡도를 증가시킨다.아래 그림 처럼 ViewModel이 View와 연동될 때 명확한 구조가 없어 개발 상황에 따라 데이터를 주고받으면서 서로 강하게 엮이는게 상황이 발생할 수 있다는 논리로 설명하고 있습니다.
TCA의 데이터 흐름
모든 구성요소의 데이터 흐름이 단방향으로 이동하고 있고 각 구성 요소는 Store로 관리되며 Dependency는 Store의 외부에서 주입해 사용하는 것을 알 수 있습니다.
TCA의 데이터 흐름은 아래 그림과 같이 표현할 수 있습니다.
- State : 비즈니스 로직을 수행하거나 UI를 그릴 때 필요한 데이터의 집합
- Action : 사용자로부터 발생하는 이벤트나 노티피케이션등 뷰에서 생길 수 있는 모든 action과 API호출 결과를 나타내는 타입입니다.
- Dependency : 외부 시스템과 상호 작용하는 유형과 기능을 말하며 대표적으로 API 클라이언트가 있고 UUID나 Date의 초기화등도 포함합니다.
- Effect : 네트워크 요청, 디스크에서 저장/로드, 타이머 생성, Dependency와 상호 작용과 같은 작업을 수행하며 Reduce()의 리턴값으로 사용됩니다.
- Reduce : Action을 전달받아 이를 처리 후 결과를 State의 상태를 변경해 UI를 업데이트하도록 하는 로직을 구현하는 메서드입니다.API 요청과 같은 이벤트를 Effect와 Dependency를 이용해 실행하고 Action을 이용해 결과를 다시 전달합니다.
- Store : 실제로 TCA의 여러 기능을 실행할 수 있는 오브젝트로 사용자 액션을 스토어로 전송하여 Reducer와 Effect를 실행할 수 있도록 하고 스토어의 상태 변화를 관찰하여 UI를 업데이트할 수 있습니다.
참고 자료
결론
MVVM 패턴 없이 사용을 해보려고 하였지만 TCA 도입을 하려면 시간비용을 많이 투자하여 도입을 해야할 것 같다. 단방향 데이터 흐름이 우리앱에 더 잘맞을 것 같지만 TCA 도입을 하기 위한 비용이 커서 이야기 해보면 좋을 것 같습니다.
728x90
'SwiftUI > 정리' 카테고리의 다른 글
[SwiftDataStructure&Algorithms] 데이터 구조 (1) | 2024.03.12 |
---|---|
XCODE IOS 프로파일링 디버깅 하는 방법 (1) | 2024.01.07 |
[SwiftUI] Combine - 기본 (0) | 2023.09.16 |
[SwiftUI] Combine (0) | 2023.09.16 |
[Swift] SwiftLint (0) | 2023.09.04 |