๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Swift

[RxSwift] ์—๋Ÿฌ ํ•ธ๋“ค๋ง

by yangsubinn 2023. 2. 9.

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