enum Event<Element> {
case next(Element) // next element of a sequence
case error(Error) // sequence failed with error
case completed // sequence terminated successfully
}
Let's discuss the pros and cons of Error being generic. If you have a generic error type, you create additional impedance mismatch between two observables.
만약 에러 타입이 제네릭이라면, 두 Observable 간에 추가적인 객체 - 관계 불일치(임피던스 미스매치) 를 발생시킬 수 있습니다.
제네릭 에러 타입을 사용하면, 각 Observable이 서로 다른 에러 타입을 가지게 될 가능성이 높아집니다. 이로 인해 두 Observable을 결합하거나 변환할 때 호환성 문제가 발생하여 추가적인 작업(변환 로직)을 필요로 하게 되고, 코드가 더 복잡해질 수 있습니다.
(impedance MisMatch 는 추후에 다룰 예정)
RxSwift에서 Error가 제네릭이 아닌 이유는 바로 이러한 “임피던스 불일치”를 줄이기 위함입니다.
에러 타입이 고정되어 있다면, 서로 다른 Observable을 결합하거나 조작할 때 에러 타입 간 변환이 필요 없으므로 코드가 간결하고 유지보수가 쉬워집니다.
반대로, 제네릭 에러 타입을 사용하면 모든 에러를 직접 변환하거나 매핑해야 하므로 복잡성이 증가합니다.
Let's say you have: Observable<String, E1> and Observable<String, E2>
There isn't much you can do with them without figuring out what the resulting error type will be.
observable<String, E1>과 Observable<String, E2>라는 두 Observable이 있습니다.
이 두 Observable을 조합하거나 처리하려면, 결과 에러 타입이 E1, E2, 아니면 새로운 E3가 될지 결정해야 합니다.
이를 해결하려면 새로운 연산자 집합이 필요합니다.
Observable도 데이터(T)와 에러(E)를 함께 다룰 수 있습니다:
- Observable<String, E1>: 문자열 데이터를 방출하며, E1 타입의 에러가 발생 가능.
- Observable<Int, E2>: 정수 데이터를 방출하며, E2 타입의 에러가 발생 가능.
eg)
Observable<Int, NetworkError>
Observable<String, ParsingError>
Observable<Data, FileError>
Observable<T, E>처럼 제네릭으로 작성하면 컴파일러가 데이터 타입과 에러 타입을 명확히 추론할 수 있습니다.
실제 사용 시, 에러 타입이 구체적일 필요가 없음
• RxSwift의 에러는 대부분 Error 프로토콜로 처리됩니다.
• 특정 에러 타입을 지정하기보다는, “어떤 에러든 발생할 수 있음”을 가정하고 설계되었습니다.
This hurts composition properties, and Rx isn’t concerned with why a sequence fails, it just usually forwards failures further down the observable chain.
이는 구성 가능성(compositional properties)을 해치며, Rx는 시퀀스가 왜 실패했는지에 대해 신경 쓰지 않고, 단순히 실패를 Observable 체인 아래로 전달합니다.
Rx의 주요 설계 철학 중 하나는 에러를 “이유”보다 “전달”에 중점을 둔다는 것입니다.
실패 원인을 복잡하게 분석하기보다는, 에러를 후속 체인으로 전달하는 단순한 방식을 선호합니다. 하지만 에러 타입 불일치가 이러한 구성을 방해할 수 있습니다.
There is an additional problem that, in some cases, operators might fail due to some internal error, in which case you wouldn’t be able to construct a resulting error and report failure.
추가적으로, 연산자가 내부 에러로 인해 실패할 경우 결과 에러를 생성하거나 실패를 보고할 수 없는 문제가 발생할 수 있습니다.
Rx 연산자는 내부적으로도 에러를 발생시킬 수 있습니다.
예를 들어, combineLatest나 merge 같은 연산자에서 내부 데이터 처리 오류가 발생한다면,
이를 적절히 처리하기 어려운 상황이 생길 수 있습니다. 이는 에러 타입 문제를 더 복잡하게 만듭니다.
But OK, let’s ignore that and assume we can use that to model sequences that don’t error out. Could it be useful for that purpose?
When you consider that case, it’s not really sufficient to only use the compiler to prove that sequences don’t error out, you also need to prove other properties. For instance, those elements are observed on MainScheduler.
하지만 일단 이런 문제를 무시하고, 에러가 발생하지 않는 시퀀스를 모델링할 수 있다고 가정해 봅시다.
그러한 목적으로 유용할 수 있을까요?
이 경우, 시퀀스가 에러를 발생시키지 않는다는 것을 컴파일러로 증명하는 것만으로는 충분하지 않으며, 다른 속성들도 증명해야 합니다. 예를 들어, 요소들이 MainScheduler에서 관찰된다는 것을 증명해야 합니다.
UI 작업에서 에러 없는 시퀀스를 사용하는 것은 안정성과 일관성을 보장하기 위함입니다.
그러나 “에러가 없다”는 것만으로는 부족합니다. 추가적으로:
1. MainScheduler에서 실행됨을 보장해야 하며,
2. 무한 스트림이어야 하며,
3. 상태가 공유되어야 합니다.
이를 RxSwift에서는 Driver와 같은 트레이트로 표현하여 개발자가 안전하게 사용할 수 있도록 합니다.
따라서, 단순히 Observable을 사용하는 것보다 에러 없는 시퀀스에 추가 속성을 결합한 트레이트를 사용하는 것이 더 적합합니다.
What you really need is a generic way to prove traits for observable sequences. There are a lot of properties you could be interested in. For example:
- sequence terminates in finite time (server side)
- sequence contains only one element (if you are running some computation)
- sequence doesn’t error out, never terminates and elements are delivered on main scheduler (UI)
- sequence doesn’t error out, never terminates and elements are delivered on main scheduler, and has refcounted sharing (UI)
- sequence doesn’t error out, never terminates and elements are delivered on specific background scheduler (audio engine)
진정으로 필요한 것은 Observable 시퀀스의 특성을 증명할 수 있는 일반적인 방법입니다.
관심을 가질 만한 속성들이 많습니다. 예를 들어:
Rx에서는 Observable이 다양한 상황에 맞는 특성을 가져야 합니다. 이를 일반적으로 관리할 수 있는 시스템이 필요합니다.
이 속성들은 다양한 사용 사례에 따라 Observable에 요구되는 특성입니다. 예를 들어, 서버 작업에서는 유한 시간 내 종료를, UI 작업에서는 메인 스케줄러에서 지속적으로 실행되는 것을 보장해야 합니다.
What you really want is a general compiler-enforced system of traits for observable sequences, and a set of invariant operators for those wanted properties.
정말로 필요한 것은 Observable 시퀀스의 특성을 컴파일러가 강제할 수 있는 일반적인 시스템과, 원하는 속성에 대한 불변 연산자(invariant operators) 집합입니다.
왜 Observable의 특성을 강제해야 하나요?
개발자가 예상치 못한 에러나 비정상적인 동작을 방지하기 위해서입니다.
특히, UI 작업처럼 MainScheduler에서만 실행되어야 하는 작업에서는 더욱 중요합니다.
불변 연산자는 왜 필요한가요?
Observable의 특성을 유지하면서 안전하게 데이터를 변환하거나 처리할 수 있기 때문입니다.
결론적으로, 이 문장이 제안하는 것은?
Observable 시퀀스의 다양한 특성을 컴파일러가 보장할 수 있는 일반적인 시스템을 구축하자는 것입니다.
이를 통해 안전성과 효율성을 향상시키고, 특성에 따라 사용할 수 있는 연산자를 명확히 구분할 수 있습니다.
이런 시스템은 이미 RxSwift의 트레이트로 어느 정도 구현되어 있지만, 이를 더 일반화하고 확장할 여지가 있다는 의미입니다.
A good analogy would be:
1, 3.14, e, 2.79, 1 + 1i <-> Observable<E>
1m/s, 1T, 5kg, 1.3 pounds <-> Errorless observable, UI observable, Finite observable ...
[1, 3.14, e, 2.79, 1 + 1i ↔ Observable]
다양한 숫자(정수, 실수, 복소수)들이 한 범주로 묶이는 것처럼,
Observable<E>는 여러 데이터 타입을 포함한 범용적인 데이터 스트림을 나타냅니다.
E는 Observable이 방출하는 데이터의 타입을 뜻하며, 숫자들처럼 다양한 값들을 처리할 수 있는 추상적인 표현입니다.
[1m/s, 1T, 5kg, 1.3 pounds ↔ Errorless observable, UI observable, Finite observable …]
물리적 단위(속도, 질량 등)가 특정 의미를 가지는 것처럼, Observable도 특성(Traits)을 가질 수 있습니다.
예를 들어:
숫자는 단위가 있어야 의미를 갖듯, Observable도 특성이 있어야 사용 목적이 명확해진다는 뜻입니다.
예시: 숫자 5는 단위 없이 의미가 불분명하지만, 5kg는 질량을 나타냄.
Observable도 단순히 데이터 스트림을 제공하는 것 이상으로, 특정 속성이나 보장이 필요.
There are many ways to do such a thing in Swift by either using composition or inheritance of observables
Swift에서는 Observable을 조합(composition)하거나 상속(inheritance)하여 이런 시스템을 구현할 수 있는 여러 방법이 있습니다.
An additional benefit of using a unit system is that you can prove that UI code is executing on the same scheduler and thus use lockless operators for all transformations.
단위 시스템을 사용하면 UI 코드가 동일한 스케줄러(MainScheduler)에서 실행된다는 것을 증명할 수 있으며, 이를 통해 모든 변환에 대해 락 없는(lockless) 연산자를 사용할 수 있습니다.
Scheduler 보장
Lockless 연산자
Since RxSwift already doesn’t have locks for single sequence operations, and all of the locks that do exist are in stateful components (e.g. UI), there are practically no locks in RxSwift code, which allows for such details to be compiler enforced.
RxSwift는 이미 단일 시퀀스 연산(single sequence operations)에는 락을 사용하지 않으며, 존재하는 락은 상태 저장 컴포넌트(UI 등)에만 적용됩니다. 따라서 RxSwift 코드에는 사실상 락이 없고, 이런 세부 사항을 컴파일러로 강제할 수 있습니다.
There really is no benefit to using typed Errors that couldn’t be achieved in other cleaner ways while preserving Rx compositional semantics.
타입이 지정된 에러(typed Errors)를 사용하는 것은, Rx의 조합적 의미론을 유지하면서 더 깔끔한 방식으로 달성할 수 있는 이점이 없다고 할 수 있습니다.
Typed Errors란?
Observable에 에러 타입을 명시적으로 지정하는 방식.
예: Observable<String, CustomError>
하지만 RxSwift는 단일 에러 타입인 Error만 지원.
왜 RxSwift는 Typed Errors를 사용하지 않는가?
에러 타입을 구분하면 조합성이 떨어집니다.
예: Observable<String, E1>과 Observable<String, E2>를 합치려면 새로운 에러 타입(E3)을 만들어야 하는 복잡성이 생김.
• 대신 Error로 통합하여, 에러를 구체적으로 처리할 필요가 없도록 설계.
• Typed Errors가 제공하는 이점은 Rx의 기존 방식(통합된 Error)에서도 충분히 달성 가능하며, 더 깔끔하고 조합적인 Rx의 특성을 유지할 수 있습니다.
[RxSwift] Scheduler.2 (1) | 2024.12.16 |
---|---|
[RxSwift] Scheduler.1 (2) | 2024.12.12 |
[RxSwift] Basic.2 (3) | 2024.11.29 |
[RxSwift] Basic.1 (2) | 2024.11.28 |
[RxSwift] Obsevables aka sequence (1) | 2024.11.27 |