RxSwift + MVVM + Clean Architecture๋ก ๊ตฌ์ฑ๋ ํ๋ก์ ํธ๋ฅผ ์์๋ก ์์ฑํ์๋๋ค.
1. ์ํ๋ error ์ enumํ์ผ๋ก ์ ์
rx์์๋ onError๋ฅผ ์ฌ์ฉํด์ error๋ฅผ ์ ๋ฌํ๋๋ฐ,
onError ์์๋ Error ํ์ ์ ๋ฐ๊ธฐ ๋๋ฌธ์ Error ํ๋กํ ์ฝ์ ์ฑํํ APIError๋ฅผ ์์ฑํด์คฌ์ต๋๋ค.
// MARK: - APIError
import Foundation
enum APIError: Error, Equatable {
case pathErr
case serverErr
case userInfoErr
case decodingErr
case duplicatedUserErr(userId: [Int])
var type: String {
switch self {
case .pathErr:
return "pathErr"
case .serverErr:
return "serverErr"
case .userInfoErr:
return "userInfoErr"
case .duplicatedUserErr:
return "duplicatedUserErr"
case .decodingErr:
return "decodingErr"
}
}
var value: [Int] {
switch self {
case .duplicatedUserErr(let userId):
return userId
default:
return []
}
}
}
2. onError๋ก ์๋ฌ ๋์ง๊ธฐ
์ฑ๊ณตํ ๊ฒฝ์ฐ(200, 201 ๋ฑ) onNext(), onCompleted()๋ฅผ ๋ณด๋ด์ค๋ค๋ฉด,
์คํจํ ๊ฒฝ์ฐ(์๋ฌ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ)์๋ onError๋ฅผ ํตํด์ ๊ทธ ์์ error๋ฅผ ๋ฃ์ด ๋ณด๋ด์ฃผ๊ฒ ๋ฉ๋๋ค.
Service
// MARK: - ~Service
func requestObjectInRx<T: Decodable>(_ target: TargetType) -> Observable<T?> {
return Observable<T?>.create { observer in
self.provider.rx
.request(target)
.subscribe { event in
switch event {
case .success(let value):
do {
let decoder = JSONDecoder()
let statusCode = value.statusCode
switch statusCode {
case 200, 201:
let body = try decoder.decode(ResponseObject<T>.self, from: value.data)
print("โ
", body)
observer.onNext(body.data)
observer.onCompleted()
...
case 401:
observer.onError(APIError.userInfoErr)
case 500:
observer.onError(APIError.serverErr)
default:
observer.onError(APIError.pathErr)
}
} catch let error {
dump(error)
observer.onError(error)
}
case .failure(let error):
dump(error)
observer.onError(error)
}
}.disposed(by: self.disposeBag)
return Disposables.create()
}
}
UseCase
clean architecture + MVVM์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์
repository๊ฐ service๋ก๋ถํฐ ์ด๋ค ์๋ฒ ํต์ ์ ์์ฒญํ๊ณ ๋ฐ์ ๊ฐ์ useCase๋ก ์ ๋ฌํ๋๋ก ํ๊ณ ,
useCase์์๋ ์๋ฌ๋ฅผ ๋ฐ์ ๊ฒฝ์ฐ, ์๋ฌ ์ข ๋ฅ์ ๋ฐ๋ผ ๋ถ๊ธฐ์ฒ๋ฆฌ๊ฐ ํ์ํ ๊ฒฝ์ฐ ๋ถ๊ธฐ์ฒ๋ฆฌํ ์ ์๋๋ก ํ์ต๋๋ค.
// MARK: - ~UseCase
var duplicatedUserId: PublishRelay<[Int]> { get set }
var networkError: PublishRelay<Error> { get set }
...
func getUserInfo(id: Int) {
repository.getUserInfo(id: id)
.subscribe(onNext: { [weak self] entity in
...
}, onError: { error in
// ํ ์ข
๋ฅ์ ์๋ฌ๋ก ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ
self.networkError.accept(error)
}).disposed(by: disposeBag)
}
func postStartMatch(requestModel: RequestModel) {
repository.postStartMatch(requestModel: requestModel)
.subscribe(onNext: { [weak self] entity in
...
}, onError: { error in
// ์๋ฌ ๋ด์์ ๋ถ๊ธฐ์ฒ๋ฆฌ๊ฐ ํ์ํ ๊ฒฝ์ฐ
// ex) ๋ ์ด์์ ์ฌ์ฉ์๋ผ๋ฆฌ ๋งค์นญ์ ํด์ฃผ๋ post api -> ์ค๋ณต๋๋ ์ ์ ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ, duplicatedUserErr ์ ๋ฌ๋ฐ์
// ๋คํธ์ํฌ ์๋ฌ์ ์ค๋ณต ์ ์ ๋๊ฐ์ง๋ก ๋ถ๊ธฐ์ฒ๋ฆฌ
guard let error = error as? APIError else { return }
if error.type == "duplicatedUserErr" {
self.duplicatedUserId.accept(error.value)
} else {
self.networkError.accept(error)
}
})
.disposed(by: disposeBag)
}
3. ์ ๋ฌ๋ฐ์ ์๋ฌ ์ฒ๋ฆฌํ๊ธฐ
useCase์ ์๋ฌ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ๋ PublishRelay๋ฅผ ๊ตฌ๋ ํ๊ณ ํด๋น ์ํ์ค์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ
ViewModel์ output์ผ๋ก ์ ๋ฌํ๊ณ ,
ViewController์์๋ ๊ทธ ViewModeld output ์ํ์ค๋ฅผ ๊ตฌ๋ ํ์ฌ ์๋ฌ ๋ฐ์์ UI์ ์ ์ฉํ ๋ถ๋ถ์ ๊ตฌํํ๋๋ก ํ์ต๋๋ค.
(alert, ํ ์คํธ๋ฉ์ธ์ง ๋ฑ)
ViewModel + ViewController
// MARK: - ~ViewModel
struct Output {
...
var networkError = PublishRelay<Void>()
}
...
useCase.networkError.subscribe { error in
// output์ผ๋ก ViewController์ ์ ๋ฌ
output.networkError.accept(())
}.disposed(by: self.disposeBag)
// MARK: - ~ViewController
private func bindViewModels() {
let input = AddPlayerViewModel.Input(...)
let output = self.viewModel.transform(from: input, disposeBag: self.disposeBag)
...
output.networkError
.subscribe { _ in
self.showNetworkAlert { _ in
self.dismiss(animated: true)
}
}.disposed(by: self.disposeBag)
}
'Swift' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Swift] MEMORY LEAK ์ก๊ธฐ (2) | 2023.02.17 |
---|---|
[Swift] RxSwift์์ Combine์ผ๋ก ๋ฐ๊ฟ๋ณด๊ธฐ (2) | 2022.09.26 |
[Swift] Array ๋ฐฐ์ด (0) | 2022.06.11 |