# Publisher - Overview

νΌλΈ”λ¦¬μ…”λŠ” μ‹œκ°„μ— 따라 λ°©μΆœν•˜λŠ” 일련의 값듀을 μ„ μ–Έν•œλ‹€. μ΄λ ‡κ²Œ 방좜된 값듀은 ν•˜λ‚˜ μ΄μƒμ˜ κ΅¬λ…μžμ—κ²Œ 전달할 수 μžˆλ‹€.

κ΅¬λ…μžμ˜ Inputκ³Ό Failureνƒ€μž…μ€ 퍼블리셔에 μ„ μ–Έλœ Output, Failureνƒ€μž…κ³Ό μΌμΉ˜ν•΄μ•Ό ν•œλ‹€. νΌλΈ”λ¦¬μ…”λŠ” κ΅¬λ…μžμ˜ ꡬ독을 받아듀이기 μœ„ν•΄ receive(subscriber:) λ©”μ„œλ“œλ₯Ό λ‚΄λΆ€μ μœΌλ‘œ κ΅¬ν˜„ν•œλ‹€. Publisher 쀑첩 ꡬ쑰체λ₯Ό μ‚΄νŽ΄λ³΄λ©΄ 내뢀에 receive ν•¨μˆ˜ νƒ€μž…μ΄ μ •μ˜λœ 것을 λ³Ό 수 μžˆλ‹€.

νΌλΈ”λ¦¬μ…”λŠ” μ•„λž˜μ˜ λ©”μ„œλ“œλ“€μ„ κ΅¬λ…μžμ—κ²Œ ν˜ΈμΆœν•  수 μžˆλ‹€.

  1. receive(subscription:): ꡬ독 μš”μ²­μ„ μŠΉμΈν•˜κ³  Subscription μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•œλ‹€. κ΅¬λ…μžλŠ” Subscription μΈμŠ€ν„΄μŠ€λ₯Ό 톡해 νΌλΈ”λ¦¬μ…”μ—κ²Œ 값을 μš”κ΅¬ν•˜κ³  κ°’μ˜ λ°©μΆœμ„ μ·¨μ†Œν•  수 μžˆλ‹€.
  2. receive(_:): νΌλΈ”λ¦¬μ…”λ‘œλΆ€ν„° ν•˜λ‚˜μ˜ 값을 κ΅¬λ…μžμ—κ²Œ μ „λ‹¬ν•œλ‹€.
  3. receive(completion:): κ΅¬λ…μžμ—κ²Œ κ°’μ˜ 방좜이 λλ‚¬μŒμ„ μ•Œλ¦°λ‹€.

λͺ¨λ“  νΌλΈ”λ¦¬μ…”λŠ” Publisherν”„λ‘œν† μ½œ 채택에 따라 receive λ©”μ„œλ“œλ₯Ό λ°˜λ“œμ‹œ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.

Publishers μ—΄κ±°ν˜• λ‚΄λΆ€ κ΅¬ν˜„μ„ μ‚΄νŽ΄λ³΄λ©΄ 수 λ§Žμ€ 퍼블리셔듀이 μ •μ˜λ˜μ–΄ μžˆλŠ” 것을 λ³Ό 수 μžˆλ‹€. μ΄λŠ” 퍼블리셔 체이닝을 μœ„ν•΄ μ˜€νΌλ ˆμ΄ν„°λ‘œ μ‚¬μš©λ  λͺ©μ μœΌλ‘œ λ§Œλ“€μ–΄μ§„ 것이닀. 예λ₯Ό λ“€μ–΄ map μ˜€νΌλ ˆμ΄ν„°μ˜ 경우 Publishers.Map클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό λ¦¬ν„΄ν•˜λ„λ‘ κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€.

Publisher vs AsyncSequence

퍼블리셔와 μŠ€μœ„ν”„νŠΈ ν‘œμ€€ 라이브러리의 AsyncSequenceλŠ” μ‹œκ°„μ— 따라 값을 λ°©μΆœν•œλ‹€λŠ” μ μ—μ„œ λ™μΌν•˜μ§€λ§Œ νΌλΈ”λ¦¬μ…”λŠ” κ΅¬λ…μžλ₯Ό 톡해 νΌλΈ”λ¦¬μ…”λ‘œλΆ€ν„° 값을 μš”μ²­ν•˜λŠ” 반면 AsyncSequenceλŠ” for-await-in을 톡해 λ°©μΆœλ˜λŠ” 값듀을 μˆœνšŒν•œλ‹€.

λ‘˜λ‹€ λͺ¨λ‘ 필터링 및 맀핑 κ΄€λ ¨ μ—°μ‚°μžλ₯Ό μ œκ³΅ν•œλ‹€.

let endpointURL = URL(string: "https://zeddios.tistory.com")!
let lineCount = endpointURL.lines.map { $0.count } βœ… // AsyncSequence μΈμŠ€ν„΄μŠ€μ˜ map μ—°μ‚°μž
do {
    for try await line in lineCount {
        print(line)
    }
} catch {

}
// 좜처: https://zeddios.tistory.com/1339 [ZeddiOS:ν‹°μŠ€ν† λ¦¬]

μ»΄λ°”μΈμ—μ„œλŠ” AsyncSequenceμ—μ„œ μ œκ³΅ν•˜μ§€ μ•ŠλŠ” μ‹œκ°„ 기반의 μ˜€νΌλ ˆμ΄ν„° debounce, throttle을 μΆ”κ°€λ‘œ μ œκ³΅ν•˜λ©° 퍼블리셔 κ°„ κ²°ν•© μ˜€νΌλ ˆμ΄ν„°μΈ merge(with:), combineLatest(_: _:) μ—°μ‚°μžλ“€λ„ μ œκ³΅ν•œλ‹€.

AsyncSequence와 μ»΄λ°”μΈμ˜ 퍼블리셔λ₯Ό λΈŒλ¦Ώμ§•ν•˜κΈ° μœ„ν•΄μ„œλŠ” νΌλΈ”λ¦¬μ…”μ—μ„œ λ°©μΆœν•˜λŠ” 값듀을 AsyncSequence둜 λ…ΈμΆœμ‹œμΌœ κ΅¬λ…μžλ₯Ό ν†΅ν•œ κ°’μ˜ μ²˜λ¦¬λ°©λ²• λŒ€μ‹  for-await-in으둜 μˆœνšŒν•  수 μžˆλ‹€.

퍼블리셔 ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜μ—¬ 직접 μ •μ˜ν•˜λŠ” λŒ€μ‹  μ»΄λ°”μΈμ—μ„œ μ œκ³΅ν•˜λŠ” λͺ‡κ°€μ§€ νƒ€μž…μ„ ν™œμš©ν•˜λ©΄ μ»€μŠ€ν…€ 퍼블리셔λ₯Ό μ‰½κ²Œ μ •μ˜ν•  수 μžˆλ‹€. PassThroughSubject와 같은 Subject ν•˜μœ„ν΄λž˜μŠ€ νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ λœλ‹€. (PassThroughSubjectλŠ” RxSwiftμ—μ„œ PublishSubject, CurrentValueSubjectλŠ” BehaviorSubject와 λ™μΌν•˜λ‹€.)

@Published ν”„λ‘œνΌν‹° 래퍼λ₯Ό μ‚¬μš©ν•˜λ©΄ μ†μ„±μ˜ 값이 λ³€ν• λ•Œλ§ˆλ‹€ 값을 λ°©μΆœν•˜λŠ” 퍼블리셔λ₯Ό μ‰½κ²Œ μ •μ˜ν•  μˆ˜λ„ μžˆλ‹€.

# Publisher - Output, Failure

Output은 νΌλΈ”λ¦¬μ…”λ‘œλΆ€ν„° λ°©μΆœλ˜λŠ” κ°’μ˜ νƒ€μž…μ„ μ˜λ―Έν•œλ‹€. FailureλŠ” 퍼블리셔가 λ°©μΆœν•  수 μžˆλŠ” μ—λŸ¬μ˜ νƒ€μž…μ„ μ˜λ―Έν•œλ‹€. 퍼블리셔가 μ—λŸ¬λ₯Ό λ°©μΆœν•˜μ§€ μ•Šμ„ 경우 Neverλ₯Ό μ‚¬μš©ν•œλ‹€.

# Operators

RxSwiftμ—μ„œ 닀뀄보지 μ•Šμ€ μ˜€νΌλ ˆμ΄ν„°λ₯Ό μΆ”κ°€λ‘œ μ •λ¦¬ν•œλ‹€.

# map, tryMap, mapError

RxSwift map μ˜€νΌλ ˆμ΄ν„°μ™€ λ™μΌν•˜λ‹€. μ—…μŠ€νŠΈλ¦Ό νΌλΈ”λ¦¬μ…”λ‘œλΆ€ν„° 받은 μ—˜λ¦¬λ¨ΌνŠΈλ₯Ό ν΄λ‘œμ €μ— 따라 λ³€ν˜•μ‹œν‚¨ λ’€ μƒˆλ‘œμš΄ μ˜΅μ €λ²„λΈ”λ‘œ λ°˜ν™˜ν•œλ‹€. tryMap은 ν΄λ‘œμ €μ—μ„œ μ—λŸ¬λ₯Ό throwν•˜λŠ” 경우 sink completionν΄λ‘œμ €μ—μ„œ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•œλ‹€.

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]

// 0μ—μ„œ μ—λŸ¬ 방좜
let cancellable = numbers.publisher
    .tryMap { try romanNumeral(from: $0) }
    .sink(
        receiveCompletion: { print ("completion: \($0)") },
        receiveValue: { print ("\($0)", terminator: " ") }
     )

mapErrorμ˜€νΌλ ˆμ΄ν„°λŠ” 방좜된 μ—λŸ¬ νƒ€μž…μ„ map처럼 λ‹€λ₯Έ ν˜•νƒœλ‘œ λ³€ν˜•ν•˜κ³  μ‹Άμ„λ•Œ μ‚¬μš©ν•œλ‹€.

struct MyGenericError: Error { var wrappedError: Error }

divisors.publisher
    .tryMap { try myDivide(1, $0) }
    .mapError { MyGenericError(wrappedError: $0) }
    // ....

# replaceNil(with:)

replaceNil μ˜€νΌλ ˆμ΄ν„°λŠ” μ—…μŠ€νŠΈλ¦Ό νΌλΈ”λ¦¬μ…”μ—μ„œ μ „λ‹¬λœ μš”μ†Œ 쀑 nil값을 with νŒŒλΌλ―Έν„°μ— μ „λ‹¬λœ κ°’μœΌλ‘œ λŒ€μ²΄ν•΄μ€€λ‹€.

let numbers: [Double?] = [1.0, 2.0, nil, 3.0]
numbers.publisher
    .replaceNil(with: 0.0)
    .sink { print("\(String(describing: $0))", terminator: " ") }
    .cancel()

# removeDuplicates

Outputνƒ€μž…μ΄ Equatable ν”„λ‘œν† μ½œμ„ κ΅¬ν˜„ν•œ 경우 λ°©μΆœλ˜λŠ” μš”μ†Œλ“€μ˜ 쀑볡을 μ œκ±°ν•˜μ—¬ λ°˜ν™˜ν•œλ‹€.

let numbers: [Int] = [1,1,2,2,3,4]
numbers.publisher
    .removeDuplicates()
    .sink(receiveValue: {print($0)})

by ν΄λ‘œμ €λ₯Ό 톡해 직접 동일성 μ—¬λΆ€ λ‘œμ§μ„ ꡬ성할 μˆ˜λ„ μžˆλ‹€.

let points = [Point(x: 0, y: 0), Point(x: 0, y: 1),
              Point(x: 1, y: 1), Point(x: 2, y: 1)]
cancellable = points.publisher
    .removeDuplicates { prev, current in
        prev.x == current.x
    }
    .sink { print("\($0)", terminator: " ") }

# replaceEmpty

μ‹œν€€μŠ€μ—μ„œ λ°©μΆœν•˜λŠ” μš”μ†Œκ°€ emptyμƒνƒœμΌλ•Œ μƒˆλ‘œμš΄ κ°’μœΌλ‘œ λŒ€μ²΄ν•œλ‹€.

let numbers: [String] = []
numbers.publisher
    .replaceEmpty(with: "ABC")
    .sink(receiveValue: { print($0) })

# collect

λ°©μΆœλ˜λŠ” μš”μ†Œλ“€μ„ μ’…ν•©ν•˜μ—¬ ν•˜λ‚˜μ˜ λ°°μ—΄λ‘œ ν•©μΉœλ‹€. countνŒŒλΌλ―Έν„°λŠ” 담을 버퍼값을 μ§€μ •ν•˜κ³  λͺ¨λ‘ λ‹΄μœΌλ©΄ λ°©μΆœν•œλ‹€. λͺ¨λ“  μš”μ†Œλ₯Ό μˆœνšŒν•˜λ©° 버퍼에 κ³„μ†ν•΄μ„œ μ±„μš΄λ‹€.

let numbers = 1..<10
numbers.publisher
    .collect()
    // collect(3) -> 3κ°œμ”© λͺ¨μ•„μ„œ λ‹€ λͺ¨μ•„지면 방좜
    .sink(receiveValue: {print($0)})

# ignoreOutput

λ°©μΆœλ˜λŠ” 값듀은 λ¬΄μ‹œν•˜κ³  completion의 failure만 λ°©μΆœν•œλ‹€.

# reduce vs scan

scan은 λˆ„μ  κ³„μ‚°λ˜λŠ” λͺ¨λ“  값을 맀번 λ°©μΆœν•˜κ³ , reduceλŠ” μ΅œμ’… μ—°μ‚°λœ λ§ˆμ§€λ§‰ 결과만 λ°©μΆœν•œλ‹€.

let numbers = 1..<4
numbers.publisher
    .scan(0) { $0 + $1 }
    .sink(receiveValue: {print($0)}) // 1 3 6

numbers.publisher
    .reduce(0, { $0 + $1 })
    .sink(receiveValue: {print($0)}) // 6

# allSatisfy

μ—…μŠ€νŠΈλ¦ΌμœΌλ‘œλΆ€ν„° μ „λ‹¬λœ μš”μ†Œ 전체가 allSatisfy μ˜€νΌλ ˆμ΄ν„°μ— μ •μ˜λœ ν΄λ‘œμ € 쑰건식에 λ§Œμ‘±λ˜λŠ”μ§€ μ—¬λΆ€λ₯Ό Boolκ°’μœΌλ‘œ λ¦¬ν„΄ν•œλ‹€.

let targetRange = (-1...100)
let numbers = [-1, 0, 10, 5]
numbers.publisher
    .allSatisfy { targetRange.contains($0) }
    .sink { print("\($0)") } // true

# drop(untilOutputFrom:), drop(while:), tryDrop(while:), dropFirst(_ count: Int)

  1. drop(untilOutputFrom:): νŒŒλΌλ―Έν„°μ— μ „λ‹¬λ˜λŠ” νΌλΈ”λ¦¬μ…”μ—μ„œ μš”μ†Œκ°€ 방좜되기 μ „κΉŒμ§€ μ—…μŠ€νŠΈλ¦Όμ—μ„œ λ°©μΆœλ˜λŠ” μš”μ†Œλ“€μ„ λ¬΄μ‹œν•œλ‹€.
  2. drop(while:): whileν΄λ‘œμ €μ— μ „λ‹¬λœ 쑰건식이 true인 λ™μ•ˆμ— μ—…μŠ€νŠΈλ¦Όμ—μ„œ λ°©μΆœλ˜λŠ” μš”μ†Œλ“€μ„ λ¬΄μ‹œν•œλ‹€.
  3. tryDrop(while:): μ—λŸ¬λ₯Ό 던질 수 μžˆλŠ” drop(while:)μ˜€νΌλ ˆμ΄ν„°
  4. dropFirst(_ count: Int): 0λ²ˆμ§ΈλΆ€ν„° countκΉŒμ§€μ˜ μš”μ†Œλ“€μ„ 버림

# append(_ elements: Self.Output...), append(_ elements: S), append

(_ publisher: P) & prepend

  1. append(_ elements: Self.Output...): Outputνƒ€μž…κ³Ό λ™μΌν•œ 데이터듀을 κ°€λ³€ νŒŒλΌλ―Έν„° ν˜•νƒœλ‘œ μ „λ‹¬ν•˜λ©΄ μ—…μŠ€νŠΈλ¦Όμ—μ„œ λ°©μΆœλ˜λŠ” μš”μ†Œμ— 이어뢙여진닀.
  2. append<S>(_ elements: S): νŒŒλΌλ―Έν„°μ— μ‹œν€€μŠ€λ₯Ό μ „λ‹¬ν•˜λ©΄ μ—…μŠ€νŠΈλ¦Όμ—μ„œ 방좜된 μ‹œν€€μŠ€μ— 이어뢙여진닀.
  3. append<P>(_ publisher: P): μ—…μŠ€νŠΈλ¦Όμ—μ„œ λ°©μΆœλ˜λŠ” μš”μ†Œμ— 이어 νŒŒλΌλ―Έν„°μ— μ „λ‹¬λœ λ‹€μš΄μŠ€νŠΈλ¦Ό νΌλΈ”λ¦¬μ…”μ—μ„œ λ°©μΆœλ˜λŠ” μš”μ†Œλ₯Ό 이어뢙인닀.
  4. prepend(_ elements: Self.Output...): append와 λ™μΌν•˜κ²Œ λ™μž‘ν•˜μ§€λ§Œ, μ‚½μž…λ˜λŠ” μœ„μΉ˜λ§Œ 맨 μ•ž

# prefix(_: Int), prefix(while: ), tryPrefix(while:), prefix

(untilOutputFrom publisher: P)

  1. prefix(Int): νŒŒλΌλ―Έν„°μ— μ „λ‹¬λœ 갯수 κ°’ 만큼 μš”μ†Œλ₯Ό λ°©μΆœν•œλ‹€.
  2. prefix(while: ): 쑰건식이 true인 λ™μ•ˆμ—λ§Œ λ°©μΆœν•˜κ³  falseκ°€ 된 μ΄ν›„μ—λŠ” λ°©μΆœν•˜μ§€ μ•ŠλŠ”λ‹€.
  3. tryPrefix: μ—λŸ¬λ₯Ό 던질 수 μžˆλŠ” prefix(while:)
  4. prefix(untilOutputFrom): λ‹€μš΄μŠ€νŠΈλ¦Ό νΌλΈ”λ¦¬μ…”μ—μ„œ μš”μ†Œλ₯Ό λ°©μΆœν•˜κΈ° μ „κΉŒμ§€ κ³„μ†ν•΄μ„œ μ—…μŠ€νŠΈλ¦Όμ˜ μš”μ†Œ 방좜
let upstream = PassthroughSubject<Int,Never>()
let second = PassthroughSubject<String,Never>()

let cancellable = upstream
    .prefix(untilOutputFrom: second)
    .sink(receiveValue: { print($0) })

upstream.send(0)
upstream.send(1)
second.send("A")
upstream.send(2)
// 0 1

# first(), first(where: (Self.Output) -> Bool), last

  1. first: ν•΄λ‹Ή μ˜€νΌλ ˆμ΄ν„° 호좜 ν›„ μ΅œμ†Œ ν•˜λ‚˜μ˜ μš”μ†Œκ°€ μ—…μŠ€νŠΈλ¦ΌμœΌλ‘œλΆ€ν„° λ°©μΆœλ λ•ŒκΉŒμ§€ unlimited μš”μ²­μ„ μ‹€ν–‰ν•œλ‹€.
  2. first(where: ): 쑰건식에 λ§žλŠ” 첫 번째 μš”μ†Œλ₯Ό λ°©μΆœν•œλ‹€
  3. last: first와 λ°˜λŒ€μ΄λ©° last(where:)도 동일

# output(at:Int), output(in range: R)

  1. output(at:): atμœ„μΉ˜μ— ν•΄λ‹Ήν•˜λŠ” μš”μ†Œ 방좜
  2. output(in: range): range에 ν•΄λ‹Ήν•˜λŠ” μš”μ†Œλ“€ 방좜, λ²”μœ„ λ²—μ–΄λ‚˜λ©΄ 아무것도 방좜 μ•ˆν•¨
let a = 1...10

a.publisher
    .output(in: 3...4)
    .sink(receiveValue: {print($0)}) // 4 5
    .cancel()

# combineLatest

let pub1 = PassthroughSubject<Int, Never>()
let pub2 = PassthroughSubject<Int, Never>()

cancellable = pub1
    .combineLatest(pub2) { (first, second) in
        return first * second
    }
    .sink { print("Result: \($0).") }

pub1.send(1)
pub1.send(2)
pub2.send(2)
pub1.send(9)
pub1.send(3)
pub2.send(12)
pub1.send(13)

combineLatestλŠ” μ—…μŠ€νŠΈλ¦Όκ³Ό λ‹€μš΄μŠ€νŠΈλ¦Ό νΌλΈ”λ¦¬μ…”μ—μ„œ λͺ¨λ‘ ν•˜λ‚˜ μ΄μƒμ˜ μš”μ†Œλ₯Ό λ°©μΆœν•˜κΈ° μ „κΉŒμ§€λŠ” 아무 μš”μ†Œλ„ λ°©μΆœν•˜μ§€ μ•ŠλŠ”λ‹€.

# merge, zip

  1. merge(with: publisher): RxSwift merge와 동일
  2. zip(_ other: publisher): 인덱슀끼리 λ¬Άμ–΄μ„œ νŠœν”Œλ°˜ν™˜

# flatMap, switchToLatest

  1. flatMap: μ—…μŠ€νŠΈλ¦Όμ—μ„œ 방좜된 μš”μ†Œλ₯Ό μƒˆλ‘œμš΄ νΌλΈ”λ¦¬μ…”λ‘œ λ¦¬ν„΄ν•˜μ—¬ κ΅¬λ…μžμ—κ²Œ μ „λ‹¬ν•œλ‹€.
    • sendλ₯Ό 톡해 μ „λ‹¬λœ νˆ¬λ‘λ¦¬μŠ€νŠΈ 포슀트 id값을 기반으둜 dataTaskPublisher 퍼블리셔λ₯Ό λ§Œλ“€μ–΄ κ΅¬λ…μžμ—κ²Œ μ „λ‹¬ν•œλ‹€.
    • 전달과 λ™μ‹œμ— μ—…μŠ€νŠΈλ¦Ό νΌλΈ”λ¦¬μ…”μ˜ λ™μž‘μ΄ completeλ˜λŠ” 것이 μ•„λ‹ˆλΌ, μƒˆλ‘œ λ¦¬ν„΄λœ 퍼블리셔 λ™μž‘μ΄ λλ‚ λ•ŒκΉŒμ§€ κΈ°λ‹€λ¦¬κ²Œ λœλ‹€. (μžλ™μ μœΌλ‘œ λΉ„λ™κΈ°μ²˜λ¦¬)
  2. switchToLatest: flatMap은 λ°©μΆœλ˜λŠ” λͺ¨λ“  μš”μ†Œμ— 따라 μƒˆλ‘œμš΄ 퍼블리셔λ₯Ό λ§€λ²ˆλ§ˆλ‹€ 생성 및 λ°©μΆœν•˜μ§€λ§Œ switchToLatestλŠ” μžλ™μœΌλ‘œ 맨 λ§ˆμ§€λ§‰ 퍼블리셔에 λŒ€ν•΄μ„œλ§Œ λ°©μΆœν•œλ‹€.
// flatMap μ˜ˆμ‹œμ½”λ“œ
struct Post: Codable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
}

var postPublisher = PassthroughSubject<Int, URLError>()

let cancellable = postPublisher.flatMap { id -> URLSession.DataTaskPublisher in
    let url = URL(string:"https://jsonplaceholder.typicode.com/posts/\(id)")!
    return URLSession.shared.dataTaskPublisher(for: url)
}
.sink(
    receiveCompletion: { completion in
        // Handle publisher completion (normal or error).
    },
    receiveValue: {
        print(try? JSONDecoder().decode(Post.self, from: $0.data))
        // Process the received data.
    }
 )
postPublisher.send(1)
postPublisher.send(2)
postPublisher.send(3)

sleep(5)
// switchToLatest μ˜ˆμ‹œμ½”λ“œ
struct User {
   let name: CurrentValueSubject<String, Never>
}

let userSubject = PassthroughSubject<User, Never>()

let cancellable = userSubject
    .map(\.name)
//    .flatMap(\.name)
    .switchToLatest()
    .sink { print($0) }

let user = User(name: .init("User 1"))
let anotherUser = User(name: .init("AnotherUser 1"))

userSubject.send(user)
userSubject.send(anotherUser)

anotherUser.name.send("AnotherUser 2")
user.name.send("User 2")

flatMap을 μ‚¬μš©ν•˜λŠ” 경우 User μΈμŠ€ν„΄μŠ€μ˜ name νΌλΈ”λ¦¬μ…”μ—μ„œ μƒˆλ‘œμš΄ μš”μ†Œ λ°©μΆœλ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ λ¬Έμžμ—΄μ„ λ°©μΆœν•˜μ—¬ κ΅¬λ…μžμ—κ²Œ μ „λ‹¬ν•œλ‹€. λ”°λΌμ„œ 좜λ ₯κ²°κ³Όκ°€ User 1, AnotherUser 1, AnotherUser 2, User 2κ°€ λœλ‹€.

μœ„ μ½”λ“œμƒμ—μ„œ userSubject에 μ „λ‹¬λ˜λŠ” 퍼블리셔λ₯Ό map으둜 λ¦¬ν„΄ν•˜κ³ , switchToLatestλ₯Ό ν˜ΈμΆœν•˜λ©΄ λ§ˆμ§€λ§‰μ— send(anotherUser) ν˜•νƒœλ‘œ μ „λ‹¬λœ 퍼블리셔가 μ™„μ „νžˆ λŒ€μ²΄ν•˜κ²Œ λœλ‹€. λ”°λΌμ„œ 좜λ ₯κ²°κ³ΌλŠ” AnotherUser에 κ΄€ν•΄μ„œλ§Œ λ‚˜νƒ€λ‚œλ‹€.

# encode, decode

struct Article: Codable {
    let title: String
    let author: String
    let pubDate: Date
}

let dataProvider = PassthroughSubject<Article, Never>()
let cancellable = dataProvider
    .encode(encoder: JSONEncoder()) // 인코딩 ..
    .decode(type: Article.self, decoder: JSONDecoder()) // λ””μ½”λ”©..
    .sink(receiveCompletion: { print ("Completion: \($0)") },
          receiveValue: {  data in
            print(data) // Article μΈμŠ€ν„΄μŠ€ 좜λ ₯
    })


dataProvider.send(Article(title: "My First Article", author: "Gita Kumar", pubDate: Date()))

# multicast, share

  1. multicast: μ—¬λŸ¬κ°œμ˜ λ‹€μš΄μŠ€νŠΈλ¦Ό 퍼블리셔가 ν•˜λ‚˜μ˜ μ—…μŠ€νŠΈλ¦Όκ³Ό μ—°κ²°λ˜μ–΄ μžˆμ„λ•Œ, μ—…μŠ€νŠΈλ¦ΌμœΌλ‘œλΆ€ν„° μ „λ‹¬λœ 이벀트 λ‹Ή ν•˜λ‚˜μ˜ receive만 호좜되기 μ›ν• λ•Œ μ‚¬μš©ν•œλ‹€. μ—…μŠ€νŠΈλ¦Ό νΌλΈ”λ¦¬μ…”μ˜ λ™μž‘μ΄ λ¬΄κ±°μšΈλ•Œ μ‚¬μš©ν•˜λ©΄ μ’‹λ‹€. ν΄λ‘œμ €μ—λŠ” ꡬ독을 κ³΅μœ ν•˜κ²Œ 될 퍼블리셔 ν•˜λ‚˜λ₯Ό μΆ”κ°€λ‘œ μ •μ˜ν•˜μ—¬ λ¦¬ν„΄ν•œλ‹€.
    • multicast ν˜ΈμΆœμ‹œ ConnectablePublisherλ₯Ό μ±„νƒν•˜λŠ” Multicast νƒ€μž… νΌλΈ”λ¦¬μ…”λ‘œ λ³€ν™˜λ˜κΈ° λ•Œλ¬Έμ— connect μ˜€νΌλ ˆμ΄ν„°λ₯Ό ν˜ΈμΆœν•΄μ€˜μ•Ό μš”μ†Œλ“€μ„ μ •μƒμ μœΌλ‘œ λ°©μΆœν•˜κ²Œ λœλ‹€.
  2. share: shareλŠ” 직접 connectλ₯Ό ν˜ΈμΆœν•  ν•„μš” 없이 λ‚΄λΆ€μ μœΌλ‘œ autoconnectλ₯Ό ν˜ΈμΆœν•œλ‹€. λ”°λΌμ„œ 첫 κ΅¬λ…μžμ˜ sinkλ•ŒλΆ€ν„° μš”μ†Œλ“€μ΄ λ°”λ‘œ λ°©μΆœλœλ‹€. ꡬ독을 퍼블리셔듀이 갖좰지고 μ—…μŠ€νŠΈλ¦ΌμœΌλ‘œλΆ€ν„° μš”μ†Œλ“€μ„ λ°©μΆœν•΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— 보톡 delay μ˜€νΌλ ˆμ΄ν„°μ™€ ν•¨κ»˜ μ‚¬μš©ν•œλ‹€.
let pub = ["First", "Second", "Third"].publisher
    .map( { return ($0, Int.random(in: 0...100)) } )
    .print("Random")

let cancellable1 = pub
   .sink { print ("Stream 1 received: \($0)")}
let cancellable2 = pub
   .sink { print ("Stream 2 received: \($0)")}

μœ„ μ½”λ“œλŠ” λ©€ν‹°μΊμŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— μ—…μŠ€νŠΈλ¦Ό pub νΌλΈ”λ¦¬μ…”μ—μ„œ λ°©μΆœλ˜λŠ” 각 μš”μ†Œμ— 독립적인 κ΅¬λ…μžκ°€ μƒμ„±λœλ‹€.

let pub = ["First", "Second", "Third"].publisher
    .map( { return ($0, Int.random(in: 0...100)) } )
    .print("Random")
    .multicast { PassthroughSubject<(String, Int), Never>() } // μΆ”κ°€


let cancellable1 = pub
   .sink { print ("Stream 1 received: \($0)")}
let cancellable2 = pub
   .sink { print ("Stream 2 received: \($0)")}
pub.connect() // μΆ”κ°€
let pub = (1...3).publisher
    .delay(for: 1, scheduler: DispatchQueue.global())
    .map( { _ in return Int.random(in: 0...100) } )
    .print("Random")
    .share()


let cancellable1 = pub
    .sink { print ("Stream 1 received: \($0)")}
let cancellable2 = pub
    .sink { print ("Stream 2 received: \($0)")}

μœ„ μ½”λ“œμ—μ„œ delayλ₯Ό μ£Όμ„μ²˜λ¦¬ν•˜λ©΄ Stream 1 received만 μ—¬λŸ¬λ²ˆ ν˜ΈμΆœλœλ‹€.

# eraseToAnyPublisher

RxSwiftμ—μ„œμ˜ asObservableκ³Ό λ™μΌν•˜λ‹€. ν•΄λ‹Ή μ˜€νΌλ ˆμ΄ν„°λ₯Ό μ‚¬μš©ν•˜λŠ” μ‹€μ œ μ΄μœ λŠ” TypeEraserλ‘œμ„œμ˜ 역할을 μ£ΌκΈ° μœ„ν•¨μ΄λ‹€. 퍼블리셔 μ„ΈλΆ€ νƒ€μž…μ„ 감좔고 AnyPublisher의 ν˜•νƒœλ‘œλ§Œ μΆ”μƒν™”ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•œλ‹€.

let x = PassthroughSubject<String, Never>()
    .flatMap { name in
        return Future<String, Error> { promise in
            promise(.success(""))
        }.catch { _ in
            Just("No user found")
        }.map { result in
            return "\(result) foo"
        }
    }
    // .eraseToAnyPublisher()
    // .sink(receiveValue: {print($0)})

μœ„ μ½”λ“œμ—μ„œ flatMap μ˜€νΌλ ˆμ΄ν„°λ‘œ λ°˜ν™˜λ˜λŠ” νΌλΈ”λ¦¬μ…”λŠ” ꡬ체 νƒ€μž…μ„ 가지고 μžˆμ–΄μ„œ 데이터 νŒŒμ΄ν”„λΌμΈμ˜ 흐름이 μ™ΈλΆ€λ‘œ λ…ΈμΆœλœλ‹€. μ–΄μ°¨ν”Ό κ΅¬λ…μžμ—κ²Œ μ „λ‹¬λ˜λŠ” 데이터λ₯Ό λ‹¨μˆœ ν™œμš©λ§Œ 할거라면 νΌλΈ”λ¦¬μ…”μ˜ ꡬ체적인 νƒ€μž…μ€ λΆˆν•„μš”ν•˜κΈ° λ•Œλ¬Έμ— eraseToAnyPublisherλ₯Ό ν˜ΈμΆœν•˜μ—¬ AnyPublisher μ˜€νΌλ ˆμ΄ν„°λ₯Ό μΆ”κ°€ν•˜λŠ” 것이닀.

# assign(toπŸ”›)

  1. assign(ReferenceWritableKeyPath<Root, Self.Output>, on object: Root): μ—…μŠ€νŠΈλ¦Όμ—μ„œ λ°©μΆœλ˜λŠ” μš”μ†Œλ“€μ„ 가지고 ν‚€νŒ¨μŠ€λ₯Ό 톡해 μΈμŠ€ν„΄μŠ€μ— 속성 할당을 μ§„ν–‰ν•œλ‹€.
  2. assign(to published: inout Published<Self.Output>.Publisher): Published속성에 직접 속성을 ν• λ‹Ήν•œλ‹€.
class MyClass {
    var anInt: Int = 0 {
        didSet {
            print("anInt was set to: \(anInt)", terminator: "; ")
        }
    }
}

var myObject = MyClass()
let myRange = (0...2)
cancellable = myRange.publisher
    .assign(to: \.anInt, on: myObject)
class MyModel2: ObservableObject {
    @Published var id: Int = 0
}
let model2 = MyModel2()
Just(100).assign(to: &model2.$id)

# AsyncPublisher

νΌλΈ”λ¦¬μ…”μ—λŠ” values속성이 μžˆλŠ”λ°, ν•΄λ‹Ή 속성을 톡해 λ°©μΆœλ˜λŠ” 값듀을 Swift의 async-await ν˜•νƒœλ‘œ 비동기적인 μˆœνšŒκ°€ κ°€λŠ₯ν•˜λ‹€. AsyncPublisherκ°€ AsyncSequenceλ₯Ό μ±„νƒν•˜κΈ° λ•Œλ¬Έμ— κ°€λŠ₯ν•œ 것

let numbers: [Int] = [1, 2, 3, 4, 5]
let filtered = numbers.publisher
    .filter { $0 % 2 == 0 }


for await number in filtered.values
{
    print("\(number)", terminator: " ")
}

# print

printλ₯Ό μ‚¬μš©ν•˜λ©΄ 디버깅이 κ°€λŠ₯ν•˜λ‹€.

let numbers: [Int] = [1, 2, 3, 4, 5]
let filtered = numbers.publisher
    .filter { $0 % 2 == 0 }
    .print("DEBUG: ")
    .sink(receiveValue: {print($0)})

# AnyPublisher

AnyPublisherλŠ” eraseToAnyPublisher μ˜€νΌλ ˆμ΄ν„°μ— λŒ€ν•œ μ„€λͺ…μ—μ„œ μ–ΈκΈ‰λ˜μ—ˆλ“― 자체적인 속성은 κ°–κ³  μžˆμ§€ μ•Šκ³ , 방좜된 μš”μ†Œ 및 μ™„λ£Œ 값을 μ „λ‹¬ν•˜κΈ° μœ„ν•œ λͺ©μ μœΌλ‘œλ§Œ μ‚¬μš©λœλ‹€.

λ‹€λ₯Έ λͺ¨λ“ˆμ—μ„œ API듀을 λ…ΈμΆœν•˜κ³  싢지 μ•Šμ„λ•Œ μ‚¬μš©ν•œλ‹€. λ˜ν•œ μ„œλΈŒμ νŠΈλ₯Ό AnyPublisher둜 λž˜ν•‘ν•˜λ©΄ μ™ΈλΆ€μ—μ„œ send λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ—¬ λΆˆν•„μš”ν•œ μš”μ†Œ λ°©μΆœμ„ 방지할 μˆ˜λ„ μžˆλ‹€.

# Published

@Published ν”„λ‘œνΌν‹° 래퍼둜 μ„ μ–Έλœ 속성은 ν•΄λ‹Ή νƒ€μž…μ— 맞게 퍼블리셔λ₯Ό μƒμ„±ν•˜κ²Œ λœλ‹€. 속성값이 λ³€κ²½λ˜λ©΄ willSetλΈ”λ‘μ—μ„œ 방좜이 이루어진닀. willSet 블둝이기 λ•Œλ¬Έμ— ν”„λ‘œνΌν‹°μ— μ‹€μ œ λ³€κ²½λœ 값이 반영되기 전에 κ΅¬λ…μžκ°€ 값을 λ°›κ²Œ λœλ‹€.

class Weather {
    @Published var temperature: Double
    init(temperature: Double) {
        self.temperature = temperature
    }
}


let weather = Weather(temperature: 20)
let cancellable = weather.$temperature
    .sink() {
        print ("Temperature now: \($0)")
}
print(weather.temperature)

weather.temperature = 25
print(weather.temperature)

temperature속성값이 25둜 λ³€κ²½λ˜μ—ˆμ„ λ•Œ κ΅¬λ…μž ν΄λ‘œμ €κ°€ λ¨Όμ € μ‹€ν–‰λ˜μ–΄ receivedκ°€ μ‹€ν–‰λ˜κ³  이후 printκ°€ μ‹€ν–‰λœλ‹€.

# Reference

  1. Zeddios - AsyncSequence (opens new window)
  2. Apple Document - Publisher (opens new window)
  3. try code by Marin Todorov - subscribe(on:) vs receive(on:) (opens new window)
  4. Velog - subscribe(on:) VS. receive(on:) (opens new window)
  5. Transforming Operators in Swift Combine Framework: Map vs FlatMap vs SwitchToLatest (opens new window)
  6. Medium - Swift Combine: TypeEraser, things you might have never known of (opens new window)