์๋ ํ์ธ์ ์ ์ธ์ ๋๋ค!
์ค๋์ Traits๊ฐ ๋ฌด์์ธ์ง์ ๋ํด ์์๋ณด๊ณ , RxSwift์ Traits์ ๋ํด ์ ๋ฆฌํด๋ณด๊ฒ ์ต๋๋ค.
(RxCocoa์ Traits๋ ๋ค์ ๊ฒ์๊ธ์์ ์ด์ด์ ์ ๋ฆฌํ๋๋ก ํ ๊ฒ์!)
์ด๋ฒ ๊ฒ์๊ธ์ ReactiveX/RxSwift์ Traits ๋ฌธ์๋ฅผ ๋ฒ์ญํ๋ฉด์ Traits์ ๋ํด ์ ๋ฆฌํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
Traits๋?
Swift๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํ์ฑ๊ณผ ์์ ์ฑ์ ํฅ์์ํค๊ณ Rx ์ฌ์ฉ์ ๋ณด๋ค ์ง๊ด์ ์ด๊ณ ๊ฐ๋จํ๊ฒ ๋ง๋๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ๊ฐ๋ ฅํ ์ ํ ์์คํ ์ ๊ฐ์ถ๊ณ ์์ต๋๋ค.
Traits๋ ์ธํฐํ์ด์ค ๊ฒฝ๊ณ๋ฅผ ๋์ด ๊ด์ฐฐ ๊ฐ๋ฅํ ์ํ์ค ์์ฑ์ ์ ๋ฌํ๊ณ ๋ณด์ฅํ๋ ๋ฐ ๋์์ด ๋ ๋ฟ๋ง ์๋๋ผ ๋ชจ๋ ์ปจํ ์คํธ์์ ์ฌ์ฉํ ์ ์๋ ์์ Observable๊ณผ ๋น๊ตํ ๋ ๋ฌธ๋งฅ์ ์ธ ์๋ฏธ๋ฅผ ์ ๊ณตํ๊ณ ๋ ๊ตฌ์ฒด์ ์ธ use-cases๋ฅผ ํ๊ฒ์ผ๋ก ํฉ๋๋ค.
Traits์ ์ฌ์ฉ์ ์ ํ์ฌํญ์ด๋ฉฐ, ์ฌ์ฉ ๋ชฉ์ ์ ์ฝ๋๋ฅผ ์ฝ๋ ์ฌ๋์๊ฒ ์๋๋ฅผ ๋ ๋ช ํํ๊ฒ ์ ๋ฌํ๊ธฐ ์ํจ์ ๋๋ค. Traits๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋๋ฅผ ๋ณด๋ค ์ง๊ด์ ์ผ๋ก ๋ง๋๋ ๋ฐ ๋์์ด ๋ ์ ์์ต๋๋ค.
Traits์ ์ฝ๊ธฐ์ ์ฉ(read-only) Observable sequence property์ ๋ฉํํ๋ ๊ตฌ์กฐ์ฒด ์ ๋๋ค.
struct Single<Element> {
let source: Observable<Element>
}
struct Driver<Element> {
let source: Observable<Element>
}
...
observable sequence์ ๋ํ ์ผ์ข ์ ๋น๋ ํจํด ๊ตฌํ์ด๋ผ๊ณ ์๊ฐํ ์ ์์ต๋๋ค.
Traits ์์ฑ ํ, .asObservable()์ ํธ์ถํ๋ฉด ๊ทธ๊ฒ์ ๋ค์ ํ๋ฒํ observable sequence๋ก ๋ณํํฉ๋๋ค.
RxSwift Traits์ ์ข ๋ฅ
RxSwift์ Traits๋ก๋ Single, Completable, Maybe๊ฐ ์์ต๋๋ค.
ํ๋์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค!
Single
Single์ ์ผ๋ จ์ ์์๋ฅผ ๋ฐฉ์ถํ๋ ๋์ ํญ์ ๋จ์ผ ์์ ๋๋ ์ค๋ฅ๋ฅผ ๋ฐฉ์ถํ๋ ๊ฒ์ ๋ณด์ฅํ๋ Observable์ ๋ณํ์ ๋๋ค .
- ์ ํํ ํ๋์ ์์ ๋๋ error๋ฅผ ๋ฐฉ์ถํฉ๋๋ค.
- ๋ถ์์์ฉ์ ๊ณต์ ํ์ง ์์ต๋๋ค.
Single์ ์ฌ์ฉํ๋ ์ผ๋ฐ์ ์ธ ์ฌ์ฉ ์ฌ๋ก ์ค ํ๋๋ ์๋ต์ด๋ ์ค๋ฅ๋ง ๋ฐํํ ์ ์๋ HTTP ์์ฒญ์ ์ํํ๋ ๊ฒฝ์ฐ์ ๋๋ค. ํ์ง๋ง, Single์ ๋ฌดํํ ์คํธ๋ฆผ ์์๊ฐ ์๋ ๋จ์ผ ์์๋ง ๊ณ ๋ คํ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Single ์์ฑํ๊ธฐ
Single์ ๋ง๋๋ ๊ฒ์ Observable์ ๋ง๋๋๊ฒ๊ณผ ๋น์ทํฉ๋๋ค. ์๋์ ๊ฐ๋จํ ์์ ๋ฅผ ํตํด ์์๋ด ์๋ค.
func getRepo(_ repo: String) -> Single<[String: Any]> {
return Single<[String: Any]>.create { single in
let task = URLSession.shared.dataTask(with: URL(string: "https://api.github.com/repos/\(repo)")!) { data, _, error in
if let error = error {
single(.failure(error))
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves),
let result = json as? [String: Any] else {
single(.failure(DataError.cantParseJSON))
return
}
single(.success(result))
}
task.resume()
return Disposables.create { task.cancel() }
}
}
์์ ํจ์๋ฅผ ๋ง๋ค๊ณ ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค. (HTTP ์์ฒญ ์ฑ๊ณต, ์คํจ์ ๋ฐ๋ฅธ ์ฒ๋ฆฌ)
getRepo("ReactiveX/RxSwift")
.subscribe { event in
switch event {
case .success(let json):
print("JSON: ", json)
case .failure(let error):
print("Error: ", error)
}
}
.disposed(by: disposeBag)
ํน์ subscribe(onSuccess:onError:) ๋ฅผ ์ด์ฉํด ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
getRepo("ReactiveX/RxSwift")
.subscribe(onSuccess: { json in
print("JSON: ", json)
},
onError: { error in
print("Error: ", error)
})
.disposed(by: disposeBag)
subscription์ Single์ ํ์ ์์๋ฅผ ํฌํจํ๋ .success, .error์ผ ์ ์๋ Swift์ Result ์ด๊ฑฐํ์ ์ฌ์ฉํฉ๋๋ค. ์ฒซ๋ฒ์งธ ์ด๋ฒคํธ ์ดํ์๋ ๋ ์ด์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
// RxSwift > Single.swift
public typealias SingleEvent<Element> = Result<Element, Swift.Error>
public func subscribe(_ observer: @escaping (SingleEvent<Element>) -> Void) -> Disposable {
var stopped = false
return self.primitiveSequence.asObservable().subscribe { event in
if stopped { return }
stopped = true
switch event {
case .next(let element):
observer(.success(element))
case .error(let error):
observer(.failure(error))
case .completed:
rxFatalErrorInDebug("Singles can't emit a completion event")
}
}
}
๋ํ, ์๋ณธ Observable sequence์ .asSingle()๋ฅผ ์ฌ์ฉํ์ฌ Single๋ก ๋ณ๊ฒฝํ์ฌ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
Completable
Completable์ ์ค์ง complete ํน์ error๋ง ๋ฐฉ์ถํ ์ ์๋ Observable์ ๋ณํ์ผ๋ก, ์ด๋ ํ ์์๋ ๋ฐฉ์ถํ์ง ์๋ ๊ฒ์ ๋ณด์ฅํฉ๋๋ค.
- ์ ๋ก ์์ ๋ฐฉ์ถ
- ์๋ฃ ์ด๋ฒคํธ ๋๋ ์๋ฌ ๋ฐฉ์ถ
- ๋ถ์์์ฉ์ ๊ณต์ ํ์ง ์์ต๋๋ค.
Completable์ ์์ ์ด ์๋ฃ๋์๋ค๋ ์ฌ์ค์๋ง ๊ด์ฌ์๊ณ ์๋ฃ๋ก ์ธํด ๋ฐ์ํ๋ ์์์๋ ๊ด์ฌ์ด ์๋ ๊ฒฝ์ฐ์ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค. Observable<Void>๋ฅผ ์ฌ์ฉํด์ ์์๋ค์ ๋ฐฉ์ถํ ์ ์๋ ๊ฒฝ์ฐ์ ๋น๊ตํ ์ ์์ต๋๋ค.
Completable ์์ฑํ๊ธฐ
Completable์ ์์ฑํ๋ ๊ฒ์ Observable ์์ฑ๊ณผ ์ ์ฌํฉ๋๋ค. ๊ฐ๋จํ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
func cacheLocally() -> Completable {
return Completable.create { completable in
// Store some data locally
...
...
guard success else {
completable(.error(CacheError.failedCaching))
return Disposables.create {}
}
completable(.completed)
return Disposables.create {}
}
}
๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
cacheLocally()
.subscribe { completable in
switch completable {
case .completed:
print("Completed with no error")
case .error(let error):
print("Completed with an error: \(error.localizedDescription)")
}
}
.disposed(by: disposeBag)
๋๋ subscribe(onCompleted:onError:)์ ์ฌ์ฉํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํฉ๋๋ค.
cacheLocally()
.subscribe(onCompleted: {
print("Completed with no error")
},
onError: { error in
print("Completed with an error: \(error.localizedDescription)")
})
.disposed(by: disposeBag)
๊ตฌ๋ ์ ์์ ์ด ์ค๋ฅ ์์ด ์๋ฃ๋์์์ ๋ํ๋ด๋ .completed ๋๋ .error์ผ ์ ์๋ CompletableEvent ์ด๊ฑฐํ์ ์ ๊ณตํฉ๋๋ค. ์ฒซ ๋ฒ์งธ ์ด๋ฒคํธ ์ดํ์๋ ๋ ์ด์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
Maybe
Maybe๋ Single๊ณผ Completable ์ฌ์ด์ ์๋ Observable์ ๋ณํ์ ๋๋ค. ๋จ์ผ ์์๋ฅผ ๋ฐฉ์ถํ๊ฑฐ๋, ์์๋ฅผ ๋ฐฉ์ถํ์ง ์๊ณ ์๋ฃํ๊ฑฐ๋, ์ค๋ฅ๋ฅผ ๋ฐฉ์ถํ ์ ์์ต๋๋ค.
Note: ์ด ์ธ ๊ฐ์ง ์ด๋ฒคํธ๋ Maybe๋ฅผ ์ข ๋ฃ์ํต๋๋ค. ์ฆ, ์ข ๋ฃ๋ Maybe๋ ์์๋ฅผ ๋ฐฉ์ถํ ์ ์๊ณ ์์๋ฅผ ๋ฐฉ์ถํ Maybe ์ญ์ ์ข ๋ฃ ์ด๋ฒคํธ๋ฅผ ๋ด๋ณด๋ผ ์ ์์ต๋๋ค. (= ์ธ ๊ฐ์ง ์ด๋ฒคํธ ์ค ํ๋๊ฐ ๋ฐฉ์ถ๋๋ฉด ์ข ๋ฃ๋ฉ๋๋ค)
- ์๋ฃ๋ ์ด๋ฒคํธ, ์ฑ๊ธ์ด๋ฒคํธ ๋๋ ์ค๋ฅ๋ฅผ ๋ฐฉ์ถํฉ๋๋ค.
- ๋ถ์์์ฉ์ ๊ณต์ ํ์ง ์์ต๋๋ค.
Maybe๋ ์์๋ฅผ ๋ฐฉ์ถํ ์ ์๋ ์ด๋ค ์์ ์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ง๋ง, ์์๋ฅผ ๋ฐ๋์ ๋ฐฉ์ถํ ํ์๋ ์์ต๋๋ค.
Maybe ์์ฑํ๊ธฐ
Maybe๋ Observabled์ ๋ง๋๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค. ๊ฐ๋จํ ์์ ๋ ์๋์ ๊ฐ์ต๋๋ค.
func generateString() -> Maybe<String> {
return Maybe<String>.create { maybe in
maybe(.success("RxSwift"))
// OR
maybe(.completed)
// OR
maybe(.error(error))
return Disposables.create {}
}
}
์๋์ ๊ฐ์ด ์ฌ์ฉํฉ๋๋ค.
generateString()
.subscribe { maybe in
switch maybe {
case .success(let element):
print("Completed with element \(element)")
case .completed:
print("Completed with no element")
case .error(let error):
print("Completed with an error \(error.localizedDescription)")
}
}
.disposed(by: disposeBag)
ํน์ subscribe(onSuccess:onError:onCompleted:)์ ์ด์ฉํด์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
generateString()
.subscribe(onSuccess: { element in
print("Completed with element \(element)")
},
onError: { error in
print("Completed with an error \(error.localizedDescription)")
},
onCompleted: {
print("Completed with no element")
})
.disposed(by: disposeBag)
.asMaybe()๋ฅผ ์ฌ์ฉํด ์์ Observable ์ํ์ค๋ฅผ Maybe๋ก ๋ณํํ๋ ๊ฒ ๋ํ ๊ฐ๋ฅํฉ๋๋ค.
์ ๋ฆฌ
RxSwift์ Traits ์ธ ๊ฐ์ง์ ๋ํด ReactiveX ๊นํ๋ธ ๊ณต์๋ฌธ์๋ฅผ ๋ฒ์ญํ๋ฉด์ ์์๋ณด์๋๋ฐ์, ์ ๊ฐ ์ดํดํ๋๋ก ์ ๋ฆฌ๋ฅผ ๊ฐ๋จํ๊ฒ ํด๋ณด๊ฒ ์ต๋๋ค.
Traits์ 'ํน์ฑ'์ด๋ผ๊ณ ์ง์ญํ ์ ์๋ฏ์ด ์ด๋ค ํน์ฑ์ด ์กด์ฌํ๋ Observable ์ํ์ค์ ๋๋ค.
Traits์ read-only Observable์ wrapper ํํ๋ก, Observable์ ๋ํ ์ ๊ทผ์ ์ ํํด์ ๋ด๋ถ์ ์กด์ฌํ๋ Observable์ ์กฐ์ ํ์ฌ ์ผ๋ถ ๊ธฐ๋ฅ์ ํนํ๋ ํํ๋ก ์ฌ์ฉ์์๊ฒ ์ ๊ณตํฉ๋๋ค.
Traits์ ์ฌ์ฉํ๋ฉด ์ฝ๋๋ฅผ ๋ณด๋ค ๋ช ํํ๊ณ ์ง๊ด์ ์ผ๋ก ๋ง๋ค ์ ์์ต๋๋ค.
1. Single
- ํญ์ ๋จ์ผ ์์ ๋๋ ์ค๋ฅ๋ฅผ ๋ฐฉ์ถ
-> ์ฑ๊ณต ์ ๋จ์ผ ์์ ๋ฐฉ์ถ, ์คํจ ์ error๋ฅผ ๋ฐฉ์ถํ๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ด๋ก๋ํ๊ฑฐ๋ ๋์คํฌ์์ ๋ก๋ํ๋ ๊ฒฝ์ฐ์ ๊ฐ์ด ์ฑ๊ณต or ์คํจ์ ๋ฐ๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๋ ์ผํ์ฑ ํ๋ก์ธ์ค์ ์ ์ฉ
2. Completable
- ์์๋ฅผ ๋ฐฉ์ถํ์ง ์๊ณ , ์ค์ง complete ํน์ error๋ง ๋ฐฉ์ถ
-> ์์ ์ ์ฑ๊ณต, ์คํจ ์ฌ๋ถ๋ง์ ํ๋จํ๋ ๊ฒฝ์ฐ ์ ์ฉ
3. Maybe
- ๋จ์ผ ์์๋ฅผ ๋ฐฉ์ถ or ์์๋ฅผ ๋ฐฉ์ถํ์ง ์๊ณ ์๋ฃ or ์ค๋ฅ ๋ฐฉ์ถ (์ธ ๊ฐ์ง ์ค ํ๋ ๋ฐฉ์ถํ๊ณ ์ข ๋ฃ๋จ)
-> ์ฑ๊ณต ๋๋ ์คํจํ ์ ์๋ ์์ ์์ ์ฑ๊ณต ์ ์ ํ์ ์ผ๋ก ๊ฐ์ ๋ฐํํด์ผ ํ๋ ๊ฒฝ์ฐ ์ ์ฉ
'๐ iOS > RxSwift' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[RxSwift] Traits - Driver, Signal, Control Property/ControlEvent (0) | 2023.04.29 |
---|---|
[RxSwift] Relay๋? (0) | 2023.02.17 |
[RxSwift] Subject๋? (0) | 2023.02.03 |
[RxSwift] Observable (0) | 2022.05.02 |
[RxSwift] RxSwift์ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ (7) | 2022.04.07 |