티스토리 뷰

728x90

Shifting time

publisher에서 방출하는 이벤트를 지연시키는 연산자를 알아보겠습니다.

delay(for:tolerance:scheduler:options)

upstream 에서 값을 방출할 때마다  잠시 동안 지연시켜 다음 지정한 스케줄러에서 방출합니다.
delay(for:tolerance:scheduler:options)


import Combine
import SwiftUI
import PlaygroundSupport
// 1
let valuesPerSecond = 1.0
let delayInSeconds = 1.5
// 2
let sourcePublisher = PassthroughSubject<Date, Never>()
// 3
let delayedPublisher = sourcePublisher.delay(for: .seconds(delayInSeconds),
                                             scheduler: DispatchQueue.main)
// 4
let subscription = Timer
    .publish(every: 1.0 / valuesPerSecond,
             on: .main,
             in: .common)
    .autoconnect()
    .subscribe(sourcePublisher)
// 5
let sourceTimeline = TimelineView(title: "Emitted values (\(valuesPerSecond) per sec.):")
// 6
let delayedTimeline = TimelineView(title: "Delayed values (with a \(delayInSeconds)s delay):")

let view = VStack(spacing: 50) {
    sourceTimeline
    delayedTimeline
}
// 7
PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

sourcePublisher.displayEvents(in: sourceTimeline)
delayedPublisher.displayEvents(in: delayedTimeline)

1. 매 1초마다 값을 방출하는 publisher를 만들고, 1.5초 후 방출하는 걸 만들고 비교
2. 날짜를 받는 Subject를 만들고
3. delay 연산자를 이용해 sourcePublisher를 1.5초 지연시킵니다.
4. 초당 1개의 값을 방출하는 타이머를 만들고, autoconnect()로 바로 시작, 구독을 합니다.
5 ~ 7. 대충 View 만들기

 

 

 

Collecting values

collect()

지정된 시간마다 값을 방출합니다.


import Combine
import SwiftUI
import PlaygroundSupport

let valuesPerSecond = 1.0
let collectTimeStride = 4

let sourcePublisher = PassthroughSubject<Date, Never>()
let collectedPublisher = sourcePublisher
    .collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))

let subscription = Timer
    .publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
    .autoconnect()
    .subscribe(sourcePublisher)

let sourceTimeline = TimelineView(title: "Emitted values:")

let collectedTimeline = TimelineView(title: "Collected values (every \(collectTimeStride)s):")

let view = VStack(spacing: 40) {
    sourceTimeline
    collectedTimeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

sourcePublisher.displayEvents(in: sourceTimeline)
collectedPublisher.displayEvents(in: collectedTimeline)

1. 4초마다 값을 구독하기 위해 collect() 연산자를 사용합니다.

+ 추가로 flatMap을 이용해서 방출됐던 값들을 마지막 4초마다 보여줄 수 있습니다.

let collectedPublisher = sourcePublisher
    .collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))
    .flatMap { dates in dates.publisher } // +추가



 

Collecting values(2)

collect(_:options:)

최대치로 설정해둔 갯수만 collect 합니다.
collect(_:options:)

import Combine
import SwiftUI
import PlaygroundSupport

let valuesPerSecond = 1.0
let collectTimeStride = 4
// 추가
let collectMaxCount = 2 

let sourcePublisher = PassthroughSubject<Date, Never>()
let collectedPublisher = sourcePublisher
    .collect(.byTime(DispatchQueue.main,
                     .seconds(collectTimeStride)))
    .flatMap { dates in dates.publisher }
// 추가
let collectedPublisher2 = sourcePublisher
    .collect(.byTimeOrCount(DispatchQueue.main,
                            .seconds(collectTimeStride),
                            collectMaxCount)) // 최대치 설정
    .flatMap { dates in dates.publisher}

let subscription = Timer
    .publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
    .autoconnect()
    .subscribe(sourcePublisher)

let sourceTimeline = TimelineView(title: "Emitted values:")

let collectedTimeline = TimelineView(title: "Collected values (every \(collectTimeStride)s):")
// 추가
let collectedTimeline2 = TimelineView(title: "Collected values (at most \(collectMaxCount) every \(collectTimeStride)s):")

let view = VStack(spacing: 40) {
    sourceTimeline
    collectedTimeline
    // 추가
    collectedTimeline2
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

sourcePublisher.displayEvents(in: sourceTimeline)
collectedPublisher.displayEvents(in: collectedTimeline)
// 추가
collectedPublisher2.displayEvents(in: collectedTimeline2)


최대치로 설정해둔 2개의 값만 collect 합니다.

 

Holding off on events

debounce(for:scheduler:)

subject에서 방출되는 값중 , for에 지정된 시간동안 입력되었던 '마지막' 값들을 한꺼번에 방출합니다.
debounce(for:scheduler:)

import Combine
import SwiftUI
import PlaygroundSupport

let subject = PassthroughSubject<String, Never>()
    // 1
let debounced = subject
    .debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
    // 2
    .share()

let subjectTimeline = TimelineView(title: "Emitted values")
let debouncedTimeline = TimelineView(title: "Debounced values")

let view = VStack(spacing: 100) {
    subjectTimeline
    debouncedTimeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

subject.displayEvents(in: subjectTimeline)
debounced.displayEvents(in: debouncedTimeline)

let subscription1 = subject
    .sink { string in
        print("+\(deltaTime)s: Subject emitted: \(string)")
    }

let subscription2 = debounced
    .sink { string in
        print("+\(deltaTime)s: Debounced emitted: \(string)")
    }

subject.feed(with: typingHelloWorld)

1. debounce를 사용해서 for에 지정된 시간동안 입력됐던 값들을 방출합니다. 
2. 여러 번 subscribe할때 동일한 결과값을 보장하기 위해 share() 를 사용합니다.

 

Throttle(for:scheduler:latest:)

subject에 방출되었던 값중, for에 지정된 시간동안 입력된 가장 '첫번째' 또는 '최근(latest)' 값을 방출합니다.
Throttle(for:scheduler:latest:)

첫번째 값 방출시 (latest: false)
import Combine
import SwiftUI
import PlaygroundSupport

let throttleDelay = 1.0

let subject = PassthroughSubject<String, Never>()

let throttled = subject
    .throttle(for: .seconds(throttleDelay),
              scheduler: DispatchQueue.main,
              latest: false)
    .share()

let subjectTimeline = TimelineView(title: "Emitted values")
let throttledTimeline = TimelineView(title: "Throttled values")

let view = VStack(spacing: 100) {
    subjectTimeline
    throttledTimeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

subject.displayEvents(in: subjectTimeline)
throttled.displayEvents(in: throttledTimeline)

let subscription1 = subject
  .sink { string in
    print("+\(deltaTime)s: Subject emitted: \(string)")
  }

let subscription2 = throttled
  .sink { string in
    print("+\(deltaTime)s: Throttled emitted: \(string)")
  }

subject.feed(with: typingHelloWorld)



최근 값 방출시 (latest: true)

let throttled = subject
    .throttle(for: .seconds(throttleDelay),
              scheduler: DispatchQueue.main,
              latest: true)
    .share()

 

 

Timing out

 

timeout(_:scheduler:options:customError:)

지정된 시간동안 이벤트 방출이없으면 complete 시킵니다.
(네트워크 응답없을시 error처리로 사용해도 될듯)

import Combine
import SwiftUI
import PlaygroundSupport

let subject = PassthroughSubject<Void, Never>()
// 1
let timedOutSubject = subject.timeout(.seconds(5),
                                      scheduler: DispatchQueue.main)
    
let timeline = TimelineView(title: "Button taps")

let view = VStack(spacing: 100) {
    Button(action: { subject.send() }) {
        Text("Press me within 5 seconds")
    }
    timeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

timedOutSubject.displayEvents(in: timeline)


1. 5초동안 이벤트 방출이 없으면 time-out (complete 처리)

timeout(_:scheduler:options:)




지정된 시간동안 이벤트 방출이 없으면 '에러처리'

import Combine
import SwiftUI
import PlaygroundSupport

// 1
enum TimeoutError: Error {
    case timeout
}

let subject = PassthroughSubject<Void, TimeoutError>() // 2

let timedOutSubject = subject.timeout(.seconds(5),
                                      scheduler: DispatchQueue.main,
                                      customError: { .timeout })  // 3
    
let timeline = TimelineView(title: "Button taps")

let view = VStack(spacing: 100) {
    Button(action: { subject.send() }) {
        Text("Press me within 5 seconds")
    }
    timeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

timedOutSubject.displayEvents(in: timeline)

1. CustomError 추가 (enum)
2. TimeoutError 리턴을 하는 상수 subject
3. timedOutSubject에서 에러가 발생할시 TimeoutError의 어떤 case를 리턴할지 정함.



 

 

 

Measuring time

 

measureInterval(using:)


value 사이의 시간을 계산하고 싶을 때 사용
   - DispatchQueue를 사용할 경우: 나노초 단위 DispatchTimelnterval로 리턴됨
   - Runloop를 사용할 경우: 초단위로 리턴됨
import Combine
import SwiftUI
import PlaygroundSupport

let subject = PassthroughSubject<String, Never>()

let measureSubject = subject.measureInterval(using: DispatchQueue.main)

let subjectTimeline = TimelineView(title: "Emitted values")
let measureTimeline = TimelineView(title: "Measured values")

let view = VStack(spacing: 100) {
    subjectTimeline
    measureTimeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

subject.displayEvents(in: subjectTimeline)
measureSubject.displayEvents(in: measureTimeline)

let subscription1 = subject.sink {
    print("+\(deltaTime)s: Subject emitted: \($0)")
}

let subscription2 = measureSubject.sink {
    print("+\(deltaTime)s: Measure emitted: \(Double($0.magnitude) / 1_000_000_000.0)")
}

let measureSubject2 = subject.measureInterval(using: RunLoop.main)

let subscription3 = measureSubject2.sink {
    print("+\(deltaTime)s: Measure2 emitted: \($0)")
}

subject.feed(with: typingHelloWorld)

let measureSubject2 = subject.measureInterval(using: RunLoop.main)

let subscription3 = measureSubject2.sink {
    print("+\(deltaTime)s: Measure2 emitted: \($0)")
}

 



 

다른건 유용하게 사용할 수 있을것 같은데 measureInterval(using:)의 경우는 아직 어떤상황에서 사용해야할지 잘 모르겠네요..

추후 더 겪어보고 사용하게 된다면 해당글 업데이트 하겠습니다 :) 

728x90