다른 값이나 시퀀스로 변형할 수 있는 operator
1주차 스터디에서 스터디원이
sink() 라는 메소드는 왜 있는 건가요??
라고 던진 질문에Publisher 안에 자체적으로 구현되어 있는 게 아닐까요?😅
라고만 답했었는데...
애초에 Publisher Operators 라는 메소드들이 공식적으로 정의가 되어 있었다!
받은 항목들을 가지고 downstream publisher나 subscriber를 만드는 메소드들을 Publisher Operators라고 한다.
Publisher Operators | Apple Developer Documentation
이 operator들은 publisher를 리턴한다.
이런 식으로..
func collect() -> Publishers.Collect<Self>
let numbers = (0...10)
cancellable = numbers.publisher
.collect()
.sink { print("\($0)") }
// Prints: "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
특정 개수만큼 항목들을 묶어서 방출하고 싶으면 파라미터로 숫자를 전달한다.
func collect(_ count: Int) -> Publishers.CollectByCount<Self>
let numbers = (0...10)
cancellable = numbers.publisher
.collect(5)
.sink { print("\($0), terminator: " "") }
// Prints "[0, 1, 2, 3, 4] [5, 6, 7, 8, 9] [10] "
→ 파라미터는 최댓값이기 때문에 파라미터로 받은 숫자보다 더 적은 개수의 항목이 묶일 수도 있음
struct Collect<Upstream> where Upstream : Publisher
struct CollectByCount<Upstream> where Upstream : Publisher
func map<T>(_ transform: @escaping (Self.Output) -> T) -> Publishers.Map<Self, T>
publisher로부터 받은 값들을 변형하는 operator
let numbers = [5, 4, 3, 2, 1, 0]
let romanNumeralDict: [Int : String] =
[1:"I", 2:"II", 3:"III", 4:"IV", 5:"V"]
cancellable = numbers.publisher
.map { romanNumeralDict[$0] ?? "(unknown)" }
.sink { print("\($0)", terminator: " ") }
// Prints: "V IV III II I (unknown)"
struct Map<Upstream, Output> where Upstream : Publisher
upstream publisher로부터 받은 항목들을 전달받은 클로저를 사용해서 변형하는 publisher
func tryMap<T>(_ transform: @escaping (Self.Output) throws -> T) -> Publishers.TryMap<Self, T>
upstream publisher로부터 받은 항목들을 에러를 던지는 클로저를 이용해서 변형하는 operator
transform
: 한 항목을 파라미터로 받고, 새 항목을 리턴하는 클로저. 클로저가 에러를 던지면 publisher는 에러와 함께 실패한다.struct ParseError: Error {}
func romanNumeral(from:Int) throws -> String {
let romanNumeralDict: [Int : String] =
[1:"I", 2:"II", 3:"III", 4:"IV", 5:"V"]
guard let numeral = romanNumeralDict[from] else {
throw ParseError()
}
return numeral
}
let numbers = [5, 4, 3, 2, 1, 0]
cancellable = numbers.publisher
.tryMap { try romanNumeral(from: $0) }
.sink(
receiveCompletion: { print ("completion: \($0)") },
receiveValue: { print ("\($0)", terminator: " ") }
)
// Prints: "V IV III II I completion: failure(ParseError())"
에러 처리가 필요하면 tryMap(_:)
을, 아니면 map(_:)
을 사용하자
struct TryMap<Upstream, Output> where Upstream : Publisher
upstream publisher로부터 받은 항목들을 에러를 던지는 클로저를 이용해서 변형하는 pubilsher
func flatMap<T, P>(
maxPublishers: Subscribers.Demand = .unlimited,
_ transform: @escaping (Self.Output) -> P
) -> Publishers.FlatMap<P, Self> where T == P.Output, P : Publisher, Self.Failure == P.Failure
Republishing Elements by Subscribing to New Publishers
새 publisher를 구독해서 항목을 새로 publish 하는 것
publisher로부터 받은 항목들을 새 publisher로 변형하는 operator
maxPublishers
: 동시에 존재하는 publisher subscription의 최댓값. 기본값은 unlimitedtransform
: 항목을 파라미터로 받고, 같은 타입의 항목을 만드는 publisher를 반환하는 클로저maxPublisher를 2로 설정했을 때 예시
public struct WeatherStation {
public let stationID: String
}
var weatherPublisher = PassthroughSubject<WeatherStation, URLError>()
cancellable = weatherPublisher.flatMap { station -> URLSession.DataTaskPublisher in
let url = URL(string:"https://weatherapi.example.com/stations/\(station.stationID)/observations/latest")!
return URLSession.shared.dataTaskPublisher(for: url)
}
.sink(
receiveCompletion: { completion in
// Handle publisher completion (normal or error).
},
receiveValue: {
// Process the received data.
}
)
weatherPublisher.send(WeatherStation(stationID: "KSFO")) // San Francisco, CA
weatherPublisher.send(WeatherStation(stationID: "EGLC")) // London, UK
weatherPublisher.send(WeatherStation(stationID: "ZBBB")) // Beijing, CN
struct FlatMap<NewPublisher, Upstream> where NewPublisher : Publisher, Upstream : Publisher, NewPublisher.Failure == Upstream.Failure
upstream publisher로부터 받은 항목을 새 publisher로 변형하는 publisher
[1, 2, 3].publisher.flatMap({ int in
return (0..<int).publisher
}).sink(receiveCompletion: { _ in }, receiveValue: { value in
print("value: \(value)")
})
/* Result:
value: 0
value: 0
value: 1
value: 0
value: 1
value: 2
*/
let tmp1 = [1, 2, 3].publisher.flatMap({ int in
return (0..<int).publisher
})
let tmp2 = [1, 2, 3].publisher.map { int in
return (0..<int).publisher
}
/*
value: Sequence<Range<Int>, Never>(sequence: Range(0..<1))
value: Sequence<Range<Int>, Never>(sequence: Range(0..<2))
value: Sequence<Range<Int>, Never>(sequence: Range(0..<3))
*/
그래서 map을 사용해서 publisher로 변형하면 output이 publisher인 publiser가 만들어지는 것!! (Publisher<NewPublisher, Error>
같은 느낌..)
하지만 flatMap을 사용하면 각 항목들을 변형한 하나의 publisher가 예쁘게 만들어진다.
func replaceNil<T>(with output: T) -> Publishers.Map<Self, T> where Self.Output == T?
스트림에 있는 nil 항목을 특정 항목으로 교체하는 operator
let numbers: [Double?] = [1.0, 2.0, nil, 3.0]
numbers.publisher
.replaceNil(with: 0.0)
.sink { print("\($0)", terminator: " ") }
// Prints: "Optional(1.0) Optional(2.0) Optional(0.0) Optional(3.0)"
func replaceEmpty(with output: Self.Output) -> Publishers.ReplaceEmpty<Self>
빈 스트림을 전달된 항목으로 바꾸는 operator
output
: upstream publisher가 아무 값도 방출되지 않고 종료되면 방출되는 항목let numbers: [Double] = []
cancellable = numbers.publisher
.replaceEmpty(with: Double.nan)
.sink { print("\($0)", terminator: " ") }
// Prints "(nan)".
Double.nan
을 방출하고 정상 종료됨
let otherNumbers: [Double] = [1.0, 2.0, 3.0]
cancellable2 = otherNumbers.publisher
.replaceEmpty(with: Double.nan)
.sink { print("\($0)", terminator: " ") }
// Prints: 1.0 2.0 3.0
방출되는 값이 있으면 replaceEmpty 로 전달한 값은 방출되지 않는다.
struct ReplaceEmpty<Upstream> where Upstream : Publisher
func scan<T>(
_ initialResult: T,
_ nextPartialResult: @escaping (T, Self.Output) -> T
) -> Publishers.Scan<Self, T>
클로저한테 마지막으로 전달받은 값과 현재 항목을 가 직고 sptream publisher로부터 받은 항목들을 변형하는 operator
initialResult
: nextPartialResult
로부터 리턴받은 이전 결과nextPartialResult
: 클로저로부터 리턴받은 이전 값과 upstream publisher로부터 방출받을 다음 항목을 인자로 받는 클로저이전에 publish된 값들을 모아서 하나의 값으로 만들고 싶을 때 사용한다.
→reduce()
랑 비슷한 느낌?
let range = (0...5)
cancellable = range.publisher
.scan(0) { return $0 + $1 }
.sink { print ("\($0)", terminator: " ") }
// Prints: "0 1 3 6 10 15 ".
struct Scan<Upstream, Output> where Upstream : Publisher