Combine学习笔记

Combine的角色

  • Publisher;负责发布事件
  • Subscriber:负责订阅事件
  • Operator:负责转换事件和数据
  • Publisher发布事件经过Operator处理中转,最终由Subscriber订阅接收

Publisher

事件发布

  • Combine 中包括 Publisher 在内的一系列角色都使用协议来进行定义
public protocol Publisher {
    associatedtype Output
    associatedtype Failure : Error
    
    func receive(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

  • Publisher 最主要的工作: 发布新的事件及其数据,准备好被 Subscriber 订阅。
  • Publisher 可以发布三种事件:
      1. 类型为 Output 的新值:这代表事件流中出现了新的值。
      1. 类型为 Failure 的错误:这代表事件流中发生了问题,事件流到此终止。
      1. 完成事件:表示事件流中所有的元素都已经发布结束,事件流到此终止。
  • 对于第一种事件,Publisher 会直接将新的值发布出来。后两种事件在 Combine 中 则使用Subscribers.Completion 来描述,Completion 是一个含有两个成员的 enum,其中成员类型为 .failure(Failure) 以及 .finished。

有限事件流和无限事件流

  • 最终会终结的事件流称为有限事件流,比如一个网络请求
  • 将不会发出 failure 或者 finished 的事件流 称为无限事件流,比如按钮的点击

如何创建Publisher(Publisher从哪里来)

  • Publishers.Sequence。顾名思义,Publishers.Sequence 接受一个 Sequence:它可以是一个数组,也可以是一个 Range。在被订阅时,Sequence 中的元素被逐个发 送出来:
check("Sequence") {
  Publishers.Sequence<[Int], Never>(sequence: [1, 2, 3])
}
check("Array") {
  [1, 2, 3].publisher
}
// 输出:
//----- Sequence -----
// receive subscription: ([1, 2, 3])
// request unlimited
// receive value: (1)
// receive value: (2)
// receive value: (3)
// receive finished

Subject

  • Subject 本身也是一个 Publisher:
public protocol Subject : AnyObject, Publisher {
  func send(_ value: Self.Output)
  func send(completion: Subscribers.Completion)
}
  • Combine 内置提供了两种常用的 Subject 类型,分别是和CurrentValueSubject。
  • PassthroughSubject 简单地将 send 接收到的事件转发给下游的其他 Publisher 或 Subscriber:
let publisher2 = PassthroughSubject() 
publisher2.send(1)
print("开始订阅") 
publisher2.sink(
  receiveCompletion: { complete in print(complete)
},
  receiveValue: { value in
    print(value)
  }
)

let s1 = check("Subject") { () -> PassthroughSubject in
let subject = PassthroughSubject() delay(1) {
    subject.send(1)
    delay(1) {
      subject.send(2)
      delay(1) {
        subject.send(completion: .finished)
      }
} }
return subject 
}
  • CurrentValueSubject 则会包装和持有一个值,并在 设置该值时发送事件并保留新的值。在订阅发生的瞬间,CurrentValueSubject 会把 当前保存的值发送给订阅者
let publisher3 = CurrentValueSubject(0)
print("开始订阅") 
publisher3.sink(
  receiveCompletion: { complete in print(complete)
},
  receiveValue: { value in
    print(value)
  }
)
publisher3.value = 1
publisher3.value = 2
publisher3.send(completion: .finished)
// 输出: !" 开始订阅 !"0
// 1
// 2
// finished
  • Just:订阅和值的发布其实是同步过程,它往往被 用来将某个值 “包装” 成一个 Publisher 来提供给各类 Publisher 的合并操作
  • Future: 订阅操作和值的发布是异步行为,不在同一时间发生的话,可以使用 Future。Future 提供了一种方式,可以让我们创建一个接受未来的事件的 Publisher。
  func testFuture() {
        Future<(Data, URLResponse), Error> { promise in
            self.loadPage(url: URL(string: "sdf")!) { data, response, error in
                if let data = data, let response = response {
                    promise(.success((data, response)))
                } else {
                    promise(.failure(error!))
                }
            }
        }
    }
  • 使用 Subject,相对于单次事件的网络请求,可以发布多次事件的操作在日常开发中更加常见:
 func testSubject() {
        let subject = PassthroughSubject()
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            subject.send(Date())
        }
    }

Foundation 中的 Publisher

  • URLSession Publisher
func test() {
   URLSession.shared
    .dataTaskPublisher(for: URL(string: "https:!"httpbin.org/get?foo=bar")!)
   .map{data,_indata}
   .decode(type: Response.self, decoder: JSONDecoder()) .compactMap { $0.args!"foo }
}
  • Timer Publisher: Foundation 中的 Timer 类型也提供了一个方法,来创建一个按照一定间隔发送事件 的 Publisher
func test() {
    let temp = check("Timer") {
        Timer.publish(every: 1, on: .main, in: .default)
    }
}
  • Notification Publisher
extension NotificationCenter {
    public func publisher(
        for name: Notification.Name,
        object: AnyObject? = nil
    ) -> NotificationCenter.Publisher
}

  func testNotificationPublisher() -> AnyPublisher  {
      NotificationCenter.default
           .publisher(for: UIApplication.didBecomeActiveNotification, object: nil)
           .map { $0.object as! Response}
           .eraseToAnyPublisher()
  }
  • @Published: Combine 中存在 @Published 封装,用来把一个 class 的属性值转变为 Publisher。它同时提供了值的存储和对外的 Publisher (通过投影符号 $ 获取)。在被 订阅时,当前值也会被发送给订阅者,它的底层其实就是一个 CurrentValueSubject
class Wrapper {
    @Published var text: String = "hoho"
}
var wrapper = Wrapper()
check("Published") {
    wrapper.$text
}
wrapper.text = "123"
wrapper.text = "abc"

Operator

  • Operator使用上游 Publisher 所发布的数据作为输入, 以此产生的新的数据,然后自身成为新的 Publisher,并将这些新的数据作为输出,发布给下游
  • 通过一系列组合,可以得到一个响应式的 Publisher 链条: 当链条最上端的 Publisher 发布某个事件后,链条中的各个 Operator 对事件和数据进行处理。在链条的末端我们希望最终能得到可以直接驱动 UI 状态的事件和数据。这样, 终端的消费者可以直接使用这些准备好的数据,而这个消费者的角色由 Subscriber 来担任。
  • Publisher的所有操作符

常用的Operator

  • print: publisher 发送事件时,将具体事件的内容打印到控制台
public func check(_ title: String, publisher: () -> P) -> AnyCancellable {
    print("----- \(title) -----")
    defer { print("") }
    return publisher()
        .print()
        .sink(
            receiveCompletion: { _ in},
            receiveValue: { value  in  print(value) }
        )
}
  • empty: 只在被订阅的时候发布一个完成事件 (receive finished)。这个 publisher 不会输出任何的 output 值,只能用于表示某个事件已经 发生。
Empty()
      .print()
      .sink(
            receiveCompletion: { _ in},
            receiveValue: { value  in  print(value) }
        )
// 输出:
// ----- Empty -----
//receive subscription: (Empty) !" request unlimited
//receive finished
  • map: 对Publisher的 output 的元素 进行变形
check("Map") {
    // 注意是在 `Publisher` 上调用了 `map`
    [1,2,3] .publisher .map{$0*2}
}

  • reduce 对Publisher中的 output 值,按照某种规则进行合并,当序列中值耗尽时,它将发布 finished事件,才会将最终的reduce后的结果发布出来。
    reduce
check("Reduce") {
  [1,2,3,4,5].publisher.reduce(0, +)
}
// 输出:
// ----- Reduce -----
//  receive subscription: (Once) !" request unlimited
// receive value: (15)
// receive finished
  • scan: 把运算的每一步结果,保存并发布出来
extension Sequence {
    public func scan(
        _ initial: ResultElement,
        _ nextPartialResult: (ResultElement, Element) -> ResultElement
    ) -> [ResultElement] {
        var result: [ResultElement] = []
        for x in self {
            result.append(nextPartialResult(result.last ?? initial, x))
        }
        return result
    }
}

func testScan() {
  check("Scan") {
    [1,2,3,4,5].publisher.scan(0, +)
  }
// ----- Scan -----
// receive subscription: ([1, 3, 6, 10, 15])
// request unlimited
// receive value: (1)
// receive value: (3)
// receive value: (6)
// receive value: (10)
// receive value: (15)
// receive finished
}
scan
  • compactMap: 将 map 结果中那些 nil 的元素去除掉,这个操作通常会 “压缩” 结果,让其中的元素数减少
func testCompactMap() {
  check("Compact Map") {
    ["1", "2", "3", "cat", "5"]
    .publisher
    .compactMap { Int($0) }
  }
// 输出:
//  ----- Compact Map -----
// receive subscription: ([1, 2, 3, 5])
}

  • flatMap flatMap 将外层 Publisher 发出的事件中的值传递给内层 Publisher,然后汇总内层 Publisher 给出的事件输出,作为最终变形后的结果。其核心目的是为了 "降维"
func testFlatMap() {
  check("Flat Map 1") {
    [[1, 2, 3], ["a", "b", "c"]]
    .publisher
    .flatMap {
        $0.publisher
    }
  }

  check("Flat Map 2") {
    ["A", "B", "C"]
    .publisher
    .flatMap { letter in
        [1, 2, 3]
          .publisher
          .map { "\(letter)\($0)" }
    } 
  }
// 输出:
// ----- Flat Map 2 -----
// receive subscription: (FlatMap) !" request unlimited
// receive value: (A1)
// receive value: (A2)
// receive value: (A3)
// receive value: (B1)
// receive value: (B2)
// receive value: (B3)
// receive value: (C1)
// receive value: (C2)
// receive value: (C3)
// receive finished

}

flatMap
  • removeDuplicates: 过滤掉这些重复的事件,进而避免无谓的额外开销
func test() {
    check("Remove Duplicates") {
        ["S", "Sw", "Sw", "Sw", "Swi","Swif", "Swift", "Swift", "Swif"]
            .publisher
            .removeDuplicates()
    }
    // 输出:
    // ----- Remove Duplicates -----
    // receive subscription: (["S", "Sw", "Swi", "Swif", "Swift", "Swif"]) !" request unlimited
    // receive value: (S)
    // receive value: (Sw)
    // receive value: (Swi)
    // receive value: (Swif)
    // receive value: (Swift)
    // receive value: (Swif)
    // receive finished
}

  • merge:它将两个事件流进行合并,在对应的时间完整保留两个事件流的全部事件:

  • mapError: Publisher 的 Failure 转换成 Subscriber 所需要的 Failure 类型:

check("Map Error") {
  Fail(error: .sampleError)
  .mapError { _ in MyError.myError }
 }
  • tryMap: 处理数据时发生的错误转变为标志事件流失败的结束事件
    func testTryMap() {
        check("Throw") {
            ["1", "2", "Swift", "4"].publisher
                .tryMap { s -> Int in
                    guard let value = Int(s) else {
                        throw MyError.myError
                    }
                    return value
                }
        }
        // 输出:
        // ----- Throw -----
        // receive subscription: (TryMap) !" request unlimited
        // receive value: (1)
        // receive value: (2)
        // receive error: (myError)
    }
  • replaceError: 从错误中恢复, replaceError 会将 Publisher 的 Failure 类型抹为 Never,比如使用 assign 来将 Publisher 绑定到 UI 上时所需要的 Failure 类型为Never。我们可以用 replaceError 来提供这样一个在出现错误时应该显示的默认值。
      check("Replace Error") {
            ["1", "Swift", "4"]
                .publisher
                .tryMap { s -> Int in
                    guard let value = Int(s) else {
                        throw MyError.myError
                    }
                    return value
                }
                .replaceError(with: -1)
        }
        
replaceError
  • catch: 上游 Publisher 发生错误时,catch 操作会使用新的 Publisher 来把原来的 Publisher 替换掉, 从实例代码中看上去输出和上面的 replaceError 没有区别,但是在 catch 的闭包中,返 回的是 Just(-1) 这个 Publisher,而不仅仅只是 Int 的 -1
    catch
     check("Catch with Just Error") {
            ["1", "Swift", "4"]
                .publisher
                .tryMap { s -> Int in
                    guard let value = Int(s) else {
                        throw MyError.myError
                    }
                    return value
                }
                .catch({_ in [-1, -2, -3].publisher })
            //                .catch({ _ in Just(-1) })
        }
  • Catch and Continue, 比如 如果我们将 (由 ["1", "2", "Swift", "4"] 构成的) 原 Publisher 看作是用户输入,将结 果的 Int 看作是最后输出,那么像上面那样的方式使用 replaceError 或者 catch 的 话,一旦用户输入了不能转为 Int 的非法值 (如 “Swift”),整个结果将永远停在我们给定的默认恢复值上,接下来的任意用户输入都将被完全忽略。这往往不是我们想要的结果,一般情况下,我们会想要后续的用户输入也能继续驱动输出,这时候我们 可以靠组合一些 Operator 来完成所需的逻辑
   check("Catch and Continue") {
            ["1", "2", "Swift", "4"]
                .publisher
                .print("[ Original ]")
                .flatMap { s in
                    return Just(s)
                        .tryMap { s -> Int in
                            guard let value = Int(s) else {
                                throw MyError.myError
                            }
                            return value
                        }
                        .print("[ TryMap ]")
                        .catch { _ in
                            Just(-1)
                                .print("[ Just ]")
                        }
                        .print("[ Catch ]")
                }
        }
  • zip: 它会把两个 (或多个) Publisher 事 件序列中在同一 index 位置上的值进行合并,也就是说,Publisher1 中的第一个事 件和 Publisher2 中的第一个事件结对合并,Publisher1 中的第二个事件和 Publisher2 中的第二个事件合并,以此类推, zip 在时序语义上更接近于 “当...且...”,当 Publisher1 发布值,且 Publisher2 发布 值时,将两个值合并,作为新的事件发布出去。在实践中,zip 经常被用在合并多个 异步事件的结果,比如同时发出了多个网络请求,希望在它们全部完成的时候把结 果合并在一起
  func testZip() {
        let subject1 = PassthroughSubject()
        let subject2 = PassthroughSubject()
        
        check("testZip") {
          subject1.zip(subject2)
        }
        subject1.send(1)
        subject2.send("A")
        subject1.send(2)
        subject2.send("B")
        subject2.send("C")
        subject2.send("D")
        subject1.send(3)
        subject1.send(4)
        subject1.send(5)
    }

// 输出:
// ----- Zip -----
//  receive subscription: (Zip) !" request unlimited
// receive value: ((1, "A")) 
// receive value: ((2, "B")) 
// receive value: ((3, "C")) 
// receive value: ((4, "D"))

  • combineLatest: 它的语义接近于 “当...或...”,当 Publisher1 发布 值,或者 Publisher2 发布值时,将两个值合并,作为新的事件发布出去。在实践中,combineLatest 被用来处理多个可变状态,在其中某一个状态发生变化 时,获取这些全部状态的最新值。
 // combineLatest
    func testCombineLatest() {
        // 它的语义接近于 “当...或...”,当 Publisher1 发布 值,或者 Publisher2 发布值时,将两个值合并,作为新的事件发布出去。
        let subject1 = PassthroughSubject()
        let subject2 = PassthroughSubject()
        
        check("combineLatest") {
          subject1.combineLatest(subject2)
        }
        
        subject1.send(1)
        subject2.send("A") // 1A
        subject1.send(2) // 2A
        subject2.send("B") //2B
        subject2.send("C")
        subject2.send("D")
        subject1.send(3)
        subject1.send(4)
        subject1.send(5)
    }

类型抹消 - eraseToAnyPublisher

  • 类型转换作用,抹除了publisher在通过Operator产生的一些中间类型,直接将最后的类型作为输出
extension Publisher {
    @inlinable
    public func eraseToAnyPublisher() -> AnyPublisher {
        return .init(self)
    }

  @inlinable
    public init(_ publisher: PublisherType)
        where Output == PublisherType.Output, Failure == PublisherType.Failure
    {
        // If this has already been boxed, avoid boxing again
        if let erased = publisher as? AnyPublisher {
            box = erased.box
        } else {
            box = PublisherBox(base: publisher)
        }
    }
}

let p1 = [[1, 2, 3], [4, 5, 6]] .publisher
.flatMap { $0.publisher }
// p1 : Publishers.FlatMap<
//    Publishers.Sequence<[Int], Never>,
//     Publishers.Sequence<[[Int]], Never>
//   >

let p2=p1.map{$0*2} 
// p2 : Publishers.Map,
//       Publishers.Sequence<[[Int]], Never>
//     >,
// Int >
let p3 = p2.eraseToAnyPublisher()  
// let p3: AnyPublisher.Output, Never>, Publishers.Sequence<[[Int]], Never>>, Int>.Failure>
// p3: AnyPublisher

let p1_ = Publishers.FlatMap(
upstream: [[1, 2, 3], [4, 5, 6]].publisher, maxPublishers: .unlimited)
{
    $0.publisher
}

let p2_ = Publishers.Map(upstream: p1_) { $0 * 2 }

   public struct FlatMap: Publisher
        where Child.Failure == Upstream.Failure
    {
        public typealias Output = Child.Output

        public typealias Failure = Upstream.Failure

        public let upstream: Upstream

        public let maxPublishers: Subscribers.Demand

        public let transform: (Upstream.Output) -> Child

        public init(upstream: Upstream, maxPublishers: Subscribers.Demand,
                    transform: @escaping (Upstream.Output) -> Child) {
            self.upstream = upstream
            self.maxPublishers = maxPublishers
            self.transform = transform
        }
        public func receive(subscriber: Downstream)
            where Child.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
        {
            let outer = Outer(downstream: subscriber,
                              maxPublishers: maxPublishers,
                              map: transform)
            subscriber.receive(subscription: outer)
            upstream.subscribe(outer)
        }
    }

 public struct Map: Publisher {

        public typealias Failure = Upstream.Failure

        /// The publisher from which this publisher receives elements.
        public let upstream: Upstream

        /// The closure that transforms elements from the upstream publisher.
        public let transform: (Upstream.Output) -> Output

        public init(upstream: Upstream,
                    transform: @escaping (Upstream.Output) -> Output) {
            self.upstream = upstream
            self.transform = transform
        }

        public func receive(subscriber: Downstream)
            where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
        {
            upstream.subscribe(Inner(downstream: subscriber, map: transform))
        }
    }

Subcriber - 订阅和绑定


public protocol Subscriber {
    associatedtype Input
    associatedtype Failure : Error
    
    func receive(subscription: Subscription)
    func receive(_ input: Self.Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion)
}

通过 sink 订阅 Publisher 事件

  • sink订阅Publisher事件中的value

let buttonClicked: AnyPublisher buttonClicked
.scan(0) { value, _ in value + 1 }
.map { String($0) }
.sink { print("Button pressed count: \($0)") }

通过 assign 绑定 Publisher 值

  • assign 所接 受的第一个参数的类型为 ReferenceWritableKeyPath,也就是说,只有 class 上用 var 声明的属性可以通过 assign 来直接赋值。
  • assign 的另一个 “限制” 是,上游 Publisher 的 Failure 的类型必须是 Never。如果 上游 Publisher 可能会发生错误,必须先对它进行处理,比如使用 replaceError 或者 catch 来把错误在绑定之前就 “消化” 掉。
let foo = Foo()
let buttonClicked: AnyPublisher buttonClicked
.scan(0) { value, _ in value + 1 } .map { String($0) }
.assign(to: \.bar, on: foo)
  • 问题:
  • 下面代码在combine内部的执行流程(见流程图或者源码)
  • store 方法为什么传入的参数是一个inout类型
   subject.eraseToAnyPublisher() 
      .sink { value in
            print(value)
        }
      .store(in: &bags)
  • 大致做了这些事情
    • 转换输出类型
    • 创建订阅者
    • 创建下游 Conduit (导管)
    • 设置订阅状态
    • 将AnyCancellable对象添加到输入bags中。AnyCancellable的生命周期由于外部输入Set bags决定.

Publisher 的引用共享 - share

  • 一个subject给多个下游subscribers发送元素
  • 上游Publisher只执行一次,下游被多个subscribers监听
  • 比如:一个网络请求被多个subcriber监听
  public init(upstream: Upstream) {
        self.inner = upstream.multicast(subject: .init()).autoconnect()
        self.upstream = upstream
}


    /// Provides a subject to deliver elements to multiple subscribers.
    ///
    /// Use a multicast publisher when you have multiple downstream subscribers, but you
    /// want upstream publishers to only process one `receive(_:)` call per event.
    /// This is useful when upstream publishers are doing expensive work you don’t want
    /// to duplicate, like performing network requests.
    ///
    public func multicast(
        subject: SubjectType
    ) -> Publishers.Multicast
        where Failure == SubjectType.Failure, Output == SubjectType.Output
    {
        return multicast { subject }
    }

  public func multicast(
        subject: SubjectType
    ) -> Publishers.Multicast
        where Failure == SubjectType.Failure, Output == SubjectType.Output
    {
        return multicast { subject }
    }

 public final class Multicast
        : ConnectablePublisher
        where Upstream.Failure == SubjectType.Failure,
              Upstream.Output == SubjectType.Output
    {
        private var lazySubject: SubjectType {
            lock.lock()
            if let subject = subject {
                lock.unlock()
                return subject
            }

            let subject = createSubject()
            self.subject = subject
            lock.unlock()
            return subject
        }

        public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
            self.upstream = upstream
            self.createSubject = createSubject
        }


        public func receive(subscriber: Downstream)
            where SubjectType.Failure == Downstream.Failure,
                  SubjectType.Output == Downstream.Input
        {
            lazySubject.subscribe(Inner(parent: self, downstream: subscriber))
        }

    }

func test() {
     let dataTaskPublisher = URLSession.shared
            .dataTaskPublisher(for: URL(string: "https://httpbin.org/get?foo=bar")!)
            .share()

        let isSuccess = dataTaskPublisher.map { data, response -> Bool in
            guard let httpRes = response as? HTTPURLResponse else { return false }
            return httpRes.statusCode // 200
        }
            .replaceError(with: false)
        
        let latestText = dataTaskPublisher
            .map{ data,_ in data}
            .decode(type: Response.self, decoder: JSONDecoder()) .compactMap { $0.args!"foo
            }
            .replaceError(with: "")
}

Cancellable, AnyCancellable 和内存管理

public final class AnyCancellable: Cancellable, Hashable {

    private var _cancel: (() -> Void)?

    public init(_ cancel: @escaping () -> Void) {
        _cancel = cancel
    }

    public init(_ canceller: OtherCancellable) {
        _cancel = canceller.cancel
    }

    public func cancel() {
        _cancel?()
        _cancel = nil
    }

    public static func == (lhs: AnyCancellable, rhs: AnyCancellable) -> Bool {
        return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
    }

    public func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

    deinit {
        _cancel?()
    }
}

Scheduler

  • A protocol that defines when and how to execute a closure.
  • Scheduler 所要解决的就 是两个问题:在什么地方 (where),以及在什么时候 (when) 来发布事件和执行代码。
  • Debounce
  • Delay
  • MeasureInterval
  • SubscribeOn
  • Throttle
  • Timeout
  • ImmediateScheduler
  • DispatchQueue
  • OperationQueue
  • RunLoop
  • Timer
func test() {
  URLSession.shared
    .dataTaskPublisher(for: URL(string: "https:!"example.com")!)
    .compactMap { String(data: $0.data, encoding: .utf8) }
    .receive(on: RunLoop.main)
  .sink(receiveCompletion: { _ in
    }, receiveValue: {
      textView.text = $0
  })
}
  • 在什么地方 (where): RunLoop 就是一个实现了 Scheduler 协议的类型,它知道要如何执行后续的订阅任 务
  • receive(on: RunLoop.main) 决定下游数据在主线程执行
  pub.sink {
     DispatchQueue.main.async {
     // Do something.
    }
   }

    /// Use this pattern instead:
    pub.receive(on: DispatchQueue.main).sink {
        // Do something.
   }
  • subscribe(on: RunLoop.main) 决定上游数据在主线程执行
    let ioPerformingPublisher == // Some publisher.
    let uiUpdatingSubscriber == // Some subscriber that updates the UI.
      ioPerformingPublisher
      .subscribe(on: backgroundQueue) // 决定 ioPerformingPublisher 在后台线程中运行
      .receive(on: RunLoop.main) // 决定 uiUpdatingSubscriber 在RunLoop.main运行
     .subscribe(uiUpdatingSubscriber)
  • 在什么时候 (when): 比较常见的两种操作是 delay 和 debounce。delay 简单地将所有事件按照一定事件 延后。debounce 则是设置了一个计时器,在事件第一次到来时,计时器启动。在计 时器有效期间,每次接收到新值,则将计时器时间重置。当且仅当计时窗口中没有新 的值到来时,最后一次事件的值才会被当作新的事件发送出去。

你可能感兴趣的:(Combine学习笔记)