Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- 애니메이션
- ios
- Protocol
- rxswift
- 리펙터링
- clean architecture
- uitableview
- collectionview
- swift documentation
- Clean Code
- UICollectionView
- HIG
- Observable
- uiscrollview
- 스위프트
- MVVM
- ribs
- Refactoring
- 클린 코드
- Xcode
- RxCocoa
- combine
- swiftUI
- 리펙토링
- 리팩토링
- SWIFT
- Human interface guide
- map
- UITextView
- tableView
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[Architecture] RxSwift, MVVM 구조 코드 본문
핵심
- subscribe할 경우, onNext를 써서 self...이렇게 접근하는 것을 지양
- viewModel에서 Observer / Observable이 아닌 그냥 변수로 선언하는 것은 지양 (flag값도 그냥 flag = false로 쓰면 좋지 않음): test case작성 시, Observer변수가 테스트하기 용이
간단한 RxSwift, MVVM구조 설계
- 토글 버튼을 클릭하면 아래 label의 텍스트가 변하는 로직
핵심은, ViewController에서는 Input에 대한 바인딩 / ViewModel에서는 transform에서 비즈니스 로직을 처리하여 Ouput으로 반환
1) ViewModel프로토콜 생성
단, 프로젝트 생성시 UnitTest를 체크해야함 (체크 안하고 RxTest를 프레임워크에 추가하면 오류발생)
protocol ViewModel: class {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
2) ViewModel구현
- 구현 전
import UIKit
import RxSwift
import RxCocoa
class ToggleVM: ViewModel {
struct Input {
}
struct Output {
}
func transform(input: Input) -> Output {
}
}
- 구현 후
class ToggleVM: ViewModel {
let bag = DisposeBag()
struct Input {
var didTapBtnToggle: Observable<Void>
}
struct Output {
var toggleCount: Driver<Int>
}
func transform(input: Input) -> Output {
let toggleCount = BehaviorRelay(value: 0)
input.didTapBtnToggle.bind(onNext: { _ in
toggleCount.accept(toggleCount.value + 1)
}).disposed(by: bag)
return Output(toggleCount: toggleCount.asDriver(onErrorJustReturn: 0))
}
}
3) ViewController구현
- viewModel, input, output변수 모두 함수 밖에서 초기화
- input 바인딩: 함수 밖에서 lazy var로 초기화
- output 바인딩: 함수 안에서 바인딩
import Foundation
import RxSwift
import RxCocoa
import UIKit
final class ToggleVC: UIViewController {
@IBOutlet weak var btnToggle: UIButton!
@IBOutlet weak var lblCount: UILabel!
let bag = DisposeBag()
let viewModel = ToggleVM()
private lazy var input = ToggleVM.Input(didTapBtnToggle: self.btnToggle.rx.tap.asObservable())
private lazy var output = self.viewModel.transform(input: input)
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
}
private func bindViewModel() {
output.toggleCount
.map { String($0) }
.drive(lblCount.rx.text)
.disposed(by: bag)
}
}
- MVVM 구조 설계 출처: github.com/sergdort/CleanArchitectureRxSwift#application-1
ViewModel에 Dependencies 담기
기본은 Input, ouput구조이지만, Dependencies를 주어, 더욱 좋은 구성
(dependencies는 router같은 외부에서 주입하도록 아래와 같이 설계하는게 베스트 모델)
class OptionVM: ViewModel {
struct Input {
}
struct Output {
}
struct Dependencies {
}
let dependencies: Dependencies
init(dependencies: Dependencies) {
self.dependencies = dependencies
}
func transform(input: Input) -> Output {
return Output()
}
}
ViewController에서 사용방법
private func setUpBindings() {
let tapGesture = UITapGestureRecognizer()
imgBackground.addGestureRecognizer(tapGesture)
let input = OptionVM.Input.init(
viewWillAppear: rx.viewWillAppear.asObservable().map { _ in },
didTapButton: btnStart.rx.tap.asObservable(),
didTapImgBackground: tapGesture.rx.event.map { _ in }.asObservable()
)
let output = input |> viewModel.transform(input: input)
output.goToSelectOption
.drive(rx.routeToSelectOption)
.disposed(by: bag)
output.error
.drive(rx.showErrorDialog)
.disposed(by: bag)
}
RxSwiftExt프레임 워크를 사용하여, 비동기를 동기적으로 처리하는 방법
pod 'RxSwiftExt'
import RxSwiftExt
// 설치
pod 'RxSwiftExt'
// import
import RxSwiftExt
ViewModel에서 transform내부, RxSwiftExt활용
- 비동기적인 것들을 마치, 동기적인 코드로 짜듯이 이어나갈 수 있음 (핵심은 materialize()메소드)
- materilize(): Observable객체를 변환 -> error이벤트가 발생하지 않으며, Event객체로 랩핑해주는 메소드 (.next, .compete, .error 생성)
- elements(): Event값 중, .next 방출 값
- errors(): Event 값 중, .error 방출 값
func transform(with dependencies: Dependencies) -> (Input) -> Output {
return { input in
// viewWillAppear
let viewWillAppearEvent = input.viewWillAppear.share().materialize()
let viewWillAppearSuccess = viewWillAppearEvent.elements()
let viewWillAppearError = viewWillAppearEvent.errors()
// requestAuthorization
let acceptTerms = viewWillAppearSuccess
.flatMap { _ in
Observable.of(UNUserNotificationCenter.current().getNotificationSettings(completionHandler:)).materialize()
}
let acceptTermsSuccess = acceptTerms.elements()
let acceptTermsError = acceptTerms.errors()
// didTapBtnStart
let didTapButtonStartEvent = input.didTapButton.share().materialize()
let didTapButtonStartNext = didTapButtonStartEvent.elements()
let didTapButtonStartError = didTapButtonStartEvent.errors()
// didTapImgBackground
let didTapImgBackgroundEvent = input.didTapImgBackground.share().materialize()
let didTapImgBackgroundNext = didTapImgBackgroundEvent.elements()
let didTapImgBackgroundError = didTapImgBackgroundEvent.errors()
// goToSelectOption
let goToSelectOption = Observable.merge(
didTapButtonStartNext.asObservable(),
didTapImgBackgroundNext.asObservable()
)
// error
let errors = Observable.merge(
viewWillAppearError,
acceptTermsError,
didTapButtonStartError,
didTapImgBackgroundError
)
return Output(
goToSelectOption: goToSelectOption.asDriver { _ in .never()},
error: errors.map { $0.localizedDescription }.asDriver { _ in .never() }
)
}
'Architecture (swift)' 카테고리의 다른 글
[iOS - swift] Mixin 패턴(mix-in), Traits 패턴 (0) | 2021.12.17 |
---|---|
[iOS - swift] Clean swift (VIP) 패턴 (기본 개념) (0) | 2021.02.13 |
[Architecture] Coordinator pattern, XCoordinator 프레임워크 [응용], PostTaskManager (0) | 2020.10.17 |
[Architecture] Coordinator pattern, XCoordinator 프레임워크 [개념] (0) | 2020.10.01 |
[Clean Architecture] 4단계의 layer로 구성된 앱의 구조 (0) | 2020.09.19 |
Comments