Combine MultipleAPI 병렬처리(MergeMany, ZipMany)
Zip 과 Merge 정의를 다시 보자
Merge
여러개의 Publisher 합쳐서 보낼때 마다 방출
각각 Publisher에서 한곳이라도 이벤트를 보내도 방출
https://developer.apple.com/documentation/combine/publisher/merge(with:)-7qt71
Zip
다른 Publisher 요소를 결합하고 튜플로 전달한다
각각 Publisher 이벤트를 한번씩은 보내야만 방출(정해지지않게 이벤트 보내도 각 Publisher 순으로 방출함)
https://developer.apple.com/documentation/combine/publishers/merge3/zip(_:)/
그렇다면 각각 이벤트를 한번씩 다 보낸 후에 방출하면 순서를 보장 받을 수 있다.
하지만 Combine에서는 ZipMany는 없으며 Zip은 최대 4개의 Publisher만 가능 하다. Observable.Zip은 있던데..
코드로 같이 보자.
// MAKR: Main
let citys = ["도시1", "도시2", "도시3", "도시4"]
let city = citys.map {
requestCity(city: $0)
}
let initial = Just<[WeatherInfo]>([])
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
let result = city.reduce(into: initial) { res, just in
res = res.zip(just) { elements, element in
return elements + [element]
}.eraseToAnyPublisher()
}
result.sink { _ in } receiveValue: { weathers in
weathers.forEach { weather in
print(weather)
}
}.store(in: &cancellable)
결과: ["도시1날씨정보", "도시2날씨정보", "도시3날씨정보", "도시4날씨정보"]
reduce를 활용하자 빈 초기값을 넣어준후에 각 이벤트마다 결과값을 합 해주는 방식으로 이벤트를 처리하였고
순서대로 도시의 날씨 값을 받을수 있다.
이렇게 쓰기엔 약간 귀찮을수 있으니 MergeMany같이 사용할 수 있도록 Publishers Extension에 추가하면 조금 더 쉽게 사용할수 있다.
extension Publishers {
struct ZipMany<element, f:="" error="">: Publisher {
typealias Output = [Element]
typealias Failure = F
private let upstreams: [AnyPublisher<element, f="">]
init(_ upstreams: [AnyPublisher<element, f="">]) {
self.upstreams = upstreams
}
func receive(subscriber: S) where Self.Failure == S.Failure, Self.Output == S.Input {
let initial = Just<[Element]>([])
.setFailureType(to: F.self)
.eraseToAnyPublisher()
let zipped = upstreams.reduce(into: initial) { result, upstream in
result = result.zip(upstream) { elements, element in
elements + [element]
}.eraseToAnyPublisher()
}
zipped.subscribe(subscriber)
}
}
}
Publishers.ZipMany(city)
.sink { error in
print(error)
} receiveValue: { weathers in
weathers.forEach { weather in
print(weather.name)
}
}.store(in: &cancellable)
이렇게 사용 함으로서 원했던 각각의 API를 쉽게 순서대로 받을수 있다
사용하면서 느낀거지만 MergeMany 와 ZipMany 잘 구분해서 사용해야한다
MergeMany의 경우 요청이 모두 비동기로 진행되어 순서에 관계없이 보낸후 온 대로 합하여 방출하는데
ZipMany의 경우 한번씩 요청을 보내고 합치는 방식으로 방출한다.
네트워크에서 오는 데이터가 큰경우 MergeMany가 더 활용도가 높아보이고
적은 데이터의 API와 순서 처리가 중요한 경우 ZipMany를 활용하는것이 좋은 방법이라 생각한다. </element,></element,></element,>
Zip gwa Merge jeong-uileul dasi boja
Merge
yeoleogaeui Publisher habchyeoseo bonaelttae mada bangchul
gaggag Publishereseo hangos-ilado ibenteuleul bonaedo bangchul
https://developer.apple.com/documentation/combine/publisher/merge(with:)-7qt71
Zip
daleun Publisher yosoleul gyeolhabhago tyupeullo jeondalhanda
gaggag Publisher ibenteuleul hanbeonssig-eun bonaeyaman bangchul(jeonghaejijianhge ibenteu bonaedo gag Publisher sun-eulo bangchulham)
https://developer.apple.com/documentation/combine/publishers/merge3/zip(_:)/
geuleohdamyeon gaggag ibenteuleul hanbeonssig da bonaen hue bangchulhamyeon sunseoleul bojang bad-eul su issda.
hajiman Combineeseoneun ZipManyneun eobs-eumyeo Zip-eun choedae 4gaeui Publisherman ganeung hada. Observable.Zip-eun issdeonde..
kodeulo gat-i boja.
// MAKR: Main
let citys = ["dosi1", "dosi2", "dosi3", "dosi4"]
let city = citys.map {
requestCity(city: $0)
}
let initial = Just<[WeatherInfo]>([])
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
let result = city.reduce(into: initial) { res, just in
res = res.zip(just) { elements, element in
return elements + [element]
}.eraseToAnyPublisher()
}
result.sink { _ in } receiveValue: { weathers in
weathers.forEach { weather in
print(weather)
}
}.store(in: &cancellable)
gyeolgwa: ["dosi1nalssijeongbo", "dosi2nalssijeongbo", "dosi3nalssijeongbo", "dosi4nalssijeongbo"]
reduceleul hwal-yonghaja bin chogigabs-eul neoh-eojunhue gag ibenteumada gyeolgwagabs-eul hab haejuneun bangsig-eulo ibenteuleul cheolihayeossgo
sunseodaelo dosiui nalssi gabs-eul bad-eulsu issda.
ileohge sseugien yaggan gwichanh-eulsu iss-euni MergeManygat-i sayonghal su issdolog Publishers Extension-e chugahamyeon jogeum deo swibge sayonghalsu issda.
extension Publishers {
struct ZipMany<element, f:="" error="">: Publisher {
typealias Output = [Element]
typealias Failure = F
private let upstreams: [AnyPublisher<element, f="">]
init(_ upstreams: [AnyPublisher<element, f="">]) {
self.upstreams = upstreams
}
func receive(subscriber: S) where Self.Failure == S.Failure, Self.Output == S.Input {
let initial = Just<[Element]>([])
.setFailureType(to: F.self)
.eraseToAnyPublisher()
let zipped = upstreams.reduce(into: initial) { result, upstream in
result = result.zip(upstream) { elements, element in
elements + [element]
}.eraseToAnyPublisher()
}
zipped.subscribe(subscriber)
}
}
}
Publishers.ZipMany(city)
.sink { error in
print(error)
} receiveValue: { weathers in
weathers.forEach { weather in
print(weather.name)
}
}.store(in: &cancellable)
ileohge sayong ham-euloseo wonhaessdeon gaggag-ui APIleul swibge sunseodaelo bad-eulsu issda
sayonghamyeonseo neukkingeojiman MergeMany wa ZipMany jal gubunhaeseo sayonghaeyahanda
MergeManyui gyeong-u yocheong-i modu bidong-gilo jinhaengdoeeo sunseoe gwangyeeobs-i bonaenhu on daelo habhayeo bangchulhaneunde
ZipManyui gyeong-u hanbeonssig yocheong-eul bonaego habchineun bangsig-eulo bangchulhanda.
neteuwokeueseo oneun deiteoga keungyeong-u MergeManyga deo hwal-yongdoga nop-aboigo
jeog-eun deiteoui APIwa sunseo cheoliga jung-yohan gyeong-u ZipManyleul hwal-yonghaneungeos-i joh-eun bangbeob-ila saeng-gaghanda. </element,></element,></element,>
개발을 하다 다수의 API를 호출해야 할 때가 있다
그럴때 Combine, RxSwift를 활용하면 쉽게 처리할 수 있는데
Combine의 경우 MergeMany가 존재한다.
MergeMnay를 사용하게 되면 다수의 API를 각각 요청 할수 있지만.. 내가 사용하면서 느꼈던 큰 문제점이 있는데
바로 순서가 보장되지 않는다는 점이다.
각 위치가 지정된 도시별 날씨 정보를 가져와야 하는 상황, 내가 관심으로 등록한 무언가를 순서대로 받아야하는 상황
요청한 순서 보장이 필요한 상황인데 보장되지않아서 난 각 요청이 모두 왔을때 순서를 다시 재배열 한적이 있다..
코드로 같이 보겠다.
// MAKR: Network
// 네트워크 요청 후 Pulibsher 반환
func requestCity(city: String) -> AnyPublisher<WeatherInfo, Error> {
return URLSession.shared.dataTaskPublisher(for: Endpoint.url(api: .weather, city: city)!)
.map { $0.data }
.decode(type: WeatherInfo.self, decoder: decode).eraseToAnyPublisher()
}
// MAKR: Main
let citys = ["도시1", "도시2", "도시3", "도시4"]
let city = citys.map {
requestCity(city: $0)
}
Publishers.MergeMany(city)
.collect()
.sink { error in
print(error)
} receiveValue: { value in
value.forEach {
print($0.name)
}
}
}.store(in: &cancellable)
// 결과
["도시3날씨정보", "도시2날씨정보", "도시1날씨정보", "도시4날씨정보"]
내가 원하는건 ["도시1", "도시2", "도시3", "도시4"] 같이 보낸 순서와 맞게 도시날씨정보를 받고싶다.
Zip 과 Merge 정의를 다시 보자
Merge
여러개의 Publisher 합쳐서 보낼때 마다 방출
각각 Publisher에서 한곳이라도 이벤트를 보내도 방출
https://developer.apple.com/documentation/combine/publisher/merge(with:)-7qt71
Zip
다른 Publisher 요소를 결합하고 튜플로 전달한다
각각 Publisher 이벤트를 한번씩은 보내야만 방출(정해지지않게 이벤트 보내도 각 Publisher 순으로 방출함)
https://developer.apple.com/documentation/combine/publishers/merge3/zip(_:)/
그렇다면 각각 이벤트를 한번씩 다 보낸 후에 방출하면 순서를 보장 받을 수 있다.
하지만 Combine에서는 ZipMany는 없으며 Zip은 최대 4개의 Publisher만 가능 하다. Observable.Zip은 있던데..
코드로 같이 보자.
// MAKR: Main
let citys = ["도시1", "도시2", "도시3", "도시4"]
let city = citys.map {
requestCity(city: $0)
}
let initial = Just<[WeatherInfo]>([])
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
let result = city.reduce(into: initial) { res, just in
res = res.zip(just) { elements, element in
return elements + [element]
}.eraseToAnyPublisher()
}
result.sink { _ in } receiveValue: { weathers in
weathers.forEach { weather in
print(weather)
}
}.store(in: &cancellable)
결과: ["도시1날씨정보", "도시2날씨정보", "도시3날씨정보", "도시4날씨정보"]
reduce를 활용하자 빈 초기값을 넣어준후에 각 이벤트마다 결과값을 합 해주는 방식으로 이벤트를 처리하였고
순서대로 도시의 날씨 값을 받을수 있다.
이렇게 쓰기엔 약간 귀찮을수 있으니 MergeMany같이 사용할 수 있도록 Publishers Extension에 추가하면 조금 더 쉽게 사용할수 있다.
extension Publishers {
struct ZipMany<Element, F: Error>: Publisher {
typealias Output = [Element]
typealias Failure = F
private let upstreams: [AnyPublisher<Element, F>]
init(_ upstreams: [AnyPublisher<Element, F>]) {
self.upstreams = upstreams
}
func receive<S: Subscriber>(subscriber: S) where Self.Failure == S.Failure, Self.Output == S.Input {
let initial = Just<[Element]>([])
.setFailureType(to: F.self)
.eraseToAnyPublisher()
let zipped = upstreams.reduce(into: initial) { result, upstream in
result = result.zip(upstream) { elements, element in
elements + [element]
}.eraseToAnyPublisher()
}
zipped.subscribe(subscriber)
}
}
}
Publishers.ZipMany(city)
.sink { error in
print(error)
} receiveValue: { weathers in
weathers.forEach { weather in
print(weather.name)
}
}.store(in: &cancellable)
이렇게 사용 함으로서 원했던 각각의 API를 쉽게 순서대로 받을수 있다
사용하면서 느낀거지만 MergeMany 와 ZipMany 잘 구분해서 사용해야한다
MergeMany의 경우 요청이 모두 비동기로 진행되어 순서에 관계없이 보낸후 온 대로 합하여 방출하는데
ZipMany의 경우 한번씩 요청을 보내고 합치는 방식으로 방출한다.
네트워크에서 오는 데이터가 큰경우 MergeMany가 더 활용도가 높아보이고
적은 데이터의 API와 순서 처리가 중요한 경우 ZipMany를 활용하는것이 좋은 방법이라 생각한다.