SetNicknameReactor
// SetNicknameReactor.swift
enum Action {
/* ... */
case confirmButtonTap(text: String)
}
enum Mutation {
/* ... */
case isValid(status: SetNicknameUseCase.TextFieldStatus)
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
/* ... */
case .confirmButtonTap(let text):
let dto = ...
return setNickNameUseCase.isValidText(dto: dto)
.map { Mutation.isValid(status: $0) }
}
SetNickNameUseCase
// SetNickNameUseCase.swift
enum TextFieldStatus {
case duplicationNickname // 중복된 닉네임
case unknownError // 알 수 없는 에러
case validNickname // 사용 가능한 닉네임
case readyToRequest // API 요청 전
}
func isValidText(dto: PostIsValidNickNameRequestDTO) -> Observable<TextFieldStatus> {
let request = setNickNameRepository
.postIsValidNickName(dto: dto)
.share()
let success = request
.compactMap { $0.element }
.map { $0 ? TextFieldStatus.validNickname : TextFieldStatus.duplicationNickname }
let fail = request
.compactMap { $0.error }
.map { _ in TextFieldStatus.unknownError }
return Observable.merge(success, fail)
}
// SetNickNameRepository.swift
func postIsValidNickName(dto: PostIsValidNickNameRequestDTO) -> Observable<Event<Bool>> {
return provider.log.rx.request(.postIsValidNickName(dto: dto))
.map(ResponseDTO<PostRefreshTokenResponseDTO>.self)
.map{ $0.isSuccess }
.asObservable()
.materialize()
}
SetNickNameRepository
// SetNickNameRepository.swift
func postIsValidNickName(dto: PostIsValidNickNameRequestDTO) -> Observable<Event<Bool>> {
return provider.log.rx.request(.postIsValidNickName(dto: dto))
.map(ResponseDTO<PostRefreshTokenResponseDTO>.self) // 응답 DTO로 parsing
.map{ $0.isSuccess } // 파싱한 데이터의 isSuccess 필드 확인
.asObservable() // Single을 Observable<Bool>로 변환
.materialize() // Observable<Event<Bool>>로 변환
}
서버 응답값은 다음과 같고, statusCode 200으로 통신이 성공하게 되면 isSuccess
값에 true을 보내준다.
struct ResponseDTO<T: Codable>: Codable {
let isSuccess: Bool
let code: String
let message: String
let data: T?
}
닉네임 중복 API의 경우, 응답값이 따로 없기 때문에 Observable<Bool>
타입으로 받게 된다. 성공/실패에 따라 Bool에 흘러가는 값이 달라지게 된다.
materialize
: Event 형태로 바꿔준다.
SetNickNameUseCase
func isValidText(dto: PostIsValidNickNameRequestDTO) -> Observable<TextFieldStatus> {
let request = setNickNameRepository
.postIsValidNickName(dto: dto)
.share() // 응답 결과는 아직 모른다. Observable<Event<Bool>> 타입
let success = request
.compactMap { $0.element }
.map { $0 ? TextFieldStatus.validNickname : TextFieldStatus.duplicationNickname }
let fail = request
.compactMap { $0.error }
.map { _ in TextFieldStatus.unknownError }
return Observable.merge(success, fail)
}
share()
사용share()
를 활용한다.compactMap { }
사용compactMap은 해당 값이 존재하면 해당 값을 방출하고, 값이 nil이면 해당 이벤트를 무시한다.
즉, compactMap { $0.element }
는 element가 있는 이벤트만을 방출하고, compactMap { $0.error }
는 오류가 발생했을 때만 그 오류를 스트림으로 방출하게 된다.
compactMap 사용 예시
let observable: Observable<Int?> = Observable.of(1, nil, 3, nil, 5)
observable
.compactMap { $0 }
.subscribe(onNext: { print($0) })
// 출력 : 1, 3, 5
enum Event
// Event.swift (RxSwift)
@frozen public enum Event<Element> {
case next(Element)
case error(Swift.Error)
case completed
}
extension Event {
// If 'next' event, returns element value
public var element: Element? {
if case .next(let value) = self {
return value
}
return nil
}
// If `error` event, returns error.
public var error: Swift.Error? {
if case .error(let error) = self {
return error
}
return nil
}
}
merge()
: success 또는 fail 둘 중 하나만 방출될 수 있따.데이터의 흐름
Observable<true>
: 사용 가능한 닉네임Observable<false>
: 중복된 닉네임 (사용 불가)Observable<Error>
: 알 수 없는 에러Observable<true>
=> .validNickname
Observable<false>
=> .duplicationNickname
Observable<Error>
=> .unknownError