Sequence contains more than one element.
RxSwift로 api 통신을 통해 로그인 기능 구현 중 다음과 같은 오류를 마주하였다.
이 오류와 함께 dispose가 되어버렸다.
오류를 해결하며 알게된 것들을 찬찬히 정리해보자!
✔️ Single
single은 traits의 종류 중 하나로 Observable과 비슷한 기능을 수행한다.
✔️ Traits이란?
Observable의 파생된 형태이다. Traits을 통해 필요한 이벤트만 사용하여 코드를 좀 더 직관적이고 명확하게 사용할 수 있도록 도와준다.
✔️ Single의 특성
Single은 항상 한 번의 흐름에 하나의 값 또는 에러를 방출한다.
Single이 방출하는 이벤트는 onSuccess와 onFailure 2가지 뿐이다.
Single을 통해 둘 중 단 하나의 이벤트만 발생함을 보장한다.
onSuccess는 onNext와 onCompleted가 합쳐진 형태로 요소를 잘 가져오고 나면 completed를 수행하여 이벤트가 종료되는 특징을 가지고 있다.
single 내부 코드를 보면 .success() 이벤트 방출 시 .next와 .completed를 동시에 수행하는 것을 볼 수 있다.
🚨 문제 발생
input.buttonTap
.throttle(.seconds(1), scheduler: MainScheduler.instance)
.flatMap {
return APIManager.shared.request(api: .login(email: self.email.value, password: self.pass.value), successType: LoginToken.self)
}
.asSingle()
.subscribe(with: self, onSuccess: { owner, result in
print(result)
}, onFailure: { owner, error in
print(error)
})
.disposed(by: disposeBag)
버튼을 클릭했을 때 api 통신을 통해 로그인 성공 여부 응답을 받는 구조이다.
네트워크 통신을 할 때에는 Single을 주로 사용한다고 하길래.. 잘 알지 못한 채 적용을 하려했다🥲
Single 타입으로 리턴이 되기 때문에 당연히 onSuccess와 onFailure 이벤트가 있는 subscribe가 나타날 줄 알았지만 flatMap을 지나오면서 Observable 타입으로 반환이 되어 asSingle()로 타입을 변경하였고
이러한 오류를 마주하게 되었다.
“시퀀스가 하나 이상의 요소를 가지고 있다”
flatMap에서 응답 결과를 asSingle()을 지나오면서 해당 오류가 발생하였다.
🔎 원인
FlatMap은 응답 결과를 하나의 시퀀스로 나열하여 Observable로 반환한다.
버튼을 처음 눌렀을 때에는 문제가 발생하지 않았지만 두번째 부터 오류가 발생하였는데, FlatMap과 Single의 특성 때문이었다.
Single은 단 하나의 요소만 가지고 성공 또는 실패를 반환한다.
FlatMap은 이벤트가 발생할 때 마다 이벤트의 응답 결과들을 하나의 시퀀스로 다시 방출되는 것이다.
이러한 flatmap의 특성을 가지고 Single타입으로 변경하려 하여 오류가 발생하고, dispose 되어 이벤트가 발생하지 않게 되었다.
또 다른 문제점을 생각해보자면..
asSingle()의 위치를 보면 button tap의 stream에 선언되어 있다. 네트워크 통신 구문 내부의 결과가 single 타입이기 때문에 asSingle()을 하게 되면 button tap의 stream이 single이 되고 그렇게 버튼 이벤트가 종료되어 더이상 요청을 할 수 없게 된다.
💡 해결
input.buttonTap
.throttle(.seconds(1), scheduler: MainScheduler.instance)
.flatMap {
return APIManager.shared.request(api: .login(email: self.email.value, password: self.pass.value), successType: LoginToken.self)
}
.subscribe(with: self, onNext: { owner, result in
switch result {
case .success(let result):
tokens.onNext(result)
case .failure(let error):
errorMsg.onNext(error.localizedDescription)
}
})
.disposed(by: disposeBag)
Single로 변경하지 않고 subscribe onNext 이벤트를 통해 처리하니 원하는 대로 해결이 되었다!!
❓ 의문..
single을 쓰니까 onSuccess와 onFailure 이벤트를 써야한다고 생각했는데 그렇지 않다면 굳이 Observable이 아닌 Single을 써야하나??
Observable과 Single의 차이점이 무엇인지에 대해 의문이 생겼다.
✅ Observable vs Single
Single은 Observable의 변형이지만 명확한 차이점이 존재한다.
Single은 위에서 설명한 바와 같이 단일 응답 결과만 방출한다. 성공 또는 실패를 수행하고 나면 이벤트가 completed가 되고, dispose가 수행된다.
Single로 작성하였던 코드를 Observable로 수정하여 디버깅 해보면
Observable은 통신이 종료된 후에도 이벤트가 dispose 되지 않는다.
Single은 이벤트가 종료된 후 바로 dispose되어 리소스가 정리되는 반면
Observable은 next 이벤트 처리 후 언제 발생할 지 모르는 다음 이벤트를 기다리기 위해 리소스가 계속 남아있게 된다.
로그인을 요청할 때에만 네트워크 통신이 이루어지는 것이므로 Single을 사용하여 리소스를 정리하는 것이 바람직하다!
✏️ 정리
Single - 하나의 요소를 방출 또는 에러를 방츨하도록 보장한다. 단일 요소를 관리해야할 때 사용
FlatMap - 모든 이벤트에 대한 결과를 하나의 시퀀스로 반환
Single vs Observable
Observable
- 오류가 발생하여 onError 이벤트로 넘어가는 것이 아니면 뷰가 사라지기 전 까지 리소스가 남아있음
- 사용자의 이벤트가 언제 발생할 지 모르기 때문에 이벤트 발생 경우를 대기하고 있어야 한다.
- stream이 계속해서 남아있기 때문에 onNext()후 onCompleted()를 통해 정리하는 방식을 사용해야 한다.
Single
- 하나의 이벤트를 처리하고 나면 이벤트가 completed 되어 종료되어 리소스가 정리된다.
- 주로 네트워크 통신 시 사용
- 네트워크 통신에서만 사용하는 것은 아니다! 하나의 응답만 다뤄야하는 경우가 있다면 사용!
- onSuccess = onNext + onCompleted
'iOS > 🚨 오류 그리고 해결' 카테고리의 다른 글
Codable TypeMismatch 오류 (0) | 2023.12.01 |
---|---|
[iOS/Swift] search bar 에 테두리와 그림자 동시 적용하기 - clipsToBounds (0) | 2023.10.01 |
[iOS/Swift] Modal Style과 LifeCycle (0) | 2023.09.07 |
테이블 뷰 셀 오류 (0) | 2023.08.03 |
멀고도 험한 AutoLayout 설정의 길 (0) | 2023.08.02 |