Swift Combine 记录

Swift Combine

A publisher is generating and sending data, operators are reacting to that data and potentially changing it, and subscribers requesting and accepting it.

Swift Operator

map

public func map<T>(_ transform: (Output) -> T) -> Just<T>
复制代码
let _ = Just(5)
    .map { value -> String in 
        switch value {
        case _ where value < 1:
            return "none"
        case _ where value == 1:
            return "one"
        case _ where value == 2:
            return "couple"
        case _ where value == 3:
            return "few"
        case _ where value > 8:
            return "many"
        default:
            return "some"
        }
    }
    .sink { receivedValue in
        print("The end result was \(receivedValue)")
    }
复制代码

Back Pressure

Back pressure Combine is designed such that the subscriber controls the flow of data, and because of that it also controls what and when processing happens in the pipeline. This is a feature of Combine called back-pressure.

Publishers

Combine provides a number of additional convenience publishers:

  • Just
  • Future
  • Deferred
  • Empty
  • Sequence
  • Fail
  • Record
  • Share
  • Multicast
  • ObservableObject
  • @Published

SwiftUI uses the @Published and @ObservedObject property wrappers, provided by Combine, to implicitly create a publisher and support its declarative view mechanisms.

Publisher receive(subscriber)

 final public func receive<S>(subscriber: S) where S : Subscriber, SubjectType.Failure == S.Failure, SubjectType.Output == S.Input
复制代码

Publisher receive(on:,options:)

您可以使用 ``Publisher/receive(on:options:)`` 操作符来接收特定调度程序上的结果和完成,例如在主运行循环上执行 UI 工作。 与影响上游消息的 ``Publisher/subscribe(on:options:)`` 相比,``Publisher/receive(on:options:)`` 改变了下游消息的执行上下文

// Specifies the scheduler on which to receive elements from the publisher.

You use the ``Publisher/receive(on:options:)`` operator to receive results and completion on a specific scheduler, such as performing UI work on the main run loop. In contrast with ``Publisher/subscribe(on:options:)``, which affects upstream messages, ``Publisher/receive(on:options:)`` changes the execution context of downstream messages.

In the following example, the ``Publisher/subscribe(on:options:)`` operator causes `jsonPublisher` to receive requests on `backgroundQueue`, while the
      ``Publisher/receive(on:options:)`` causes `labelUpdater` to receive elements and completion on `RunLoop.main`.
     
let jsonPublisher = MyJSONLoaderPublisher() // Some publisher.
 let labelUpdater = MyLabelUpdateSubscriber() // Some subscriber that updates the UI.
     
 jsonPublisher .subscribe(on: backgroundQueue)
              .receive(on: RunLoop.main)
              .subscribe(labelUpdater)


在订阅者执行时更倾向于使用 Publisher/receive(on:options:)
/// Prefer ``Publisher/receive(on:options:)`` over explicit use of dispatch queues when performing work in subscribers. For example, instead of the following pattern:
    ///
    ///     pub.sink {
    ///         DispatchQueue.main.async {
    ///             // Do something.
    ///         }
    ///     }
    ///
    /// Use this pattern instead:
    ///
    ///     pub.receive(on: DispatchQueue.main).sink {
    ///         // Do something.
    ///     }
    ///
    ///  > Note: ``Publisher/receive(on:options:)`` doesn’t affect the scheduler used to call the subscriber’s ``Subscriber/receive(subscription:)`` method.
复制代码

指明调度器

public func receive<S>(on scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.ReceiveOn<Self, S> where S : Scheduler
复制代码

Publisher subscribe(subject)

>  Attaches the specified subject to this publisher.
 
>  Parameter subject: The subject to attach to this publisher.
  public func subscribe<S>(_ subject: S) -> AnyCancellable where S : Subject, Self.Failure == S.Failure, Self.Output == S.Output

复制代码

内置Subject

Combine 有两种内置主题 CurrentValueSubjectPassthroughSubject 它们的行为类似,不同之处在于 CurrentValueSubject 记住并需要初始状态,而 PassthroughSubject 则不需要。两者都会在.send()被调用时向任何订阅者提供更新的值

订阅者 subscriber

Combine 内置了两个订阅者:AssignSink。SwiftUI 内置了一个订阅者:onReceive

assign(to:,on:)

    /// Assigns each element from a publisher to a property on an object.
    ///
    /// Use the ``Publisher/assign(to:on:)`` subscriber when you want to set a given property each time a publisher produces a value.
    ///
    /// In this example, the ``Publisher/assign(to:on:)`` sets the value of the `anInt` property on an instance of `MyClass`:
    ///
    ///     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)
    ///
    ///     // Prints: "anInt was set to: 0; anInt was set to: 1; anInt was set to: 2"
    ///
    ///  > Important: The ``Subscribers/Assign`` instance created by this operator maintains a strong reference to `object`, and sets it to `nil` when the upstream publisher completes (either normally or with an error).
    ///
    /// - Parameters:
    ///   - keyPath: A key path that indicates the property to assign. See [Key-Path Expression](https://developer.apple.com/library/archive/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID563) in _The Swift Programming Language_ to learn how to use key paths to specify a property of an object.
    ///   - object: The object that contains the property. The subscriber assigns the object’s property every time it receives a new value.
    /// - Returns: An ``AnyCancellable`` instance. Call ``Cancellable/cancel()`` on this instance when you no longer want the publisher to automatically assign the property. Deinitializing this instance will also cancel automatic assignment.
   
 public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable
复制代码

sink(receiveCompletion:@escaping (()->Void),receiveValue:@escaping((output)->Void)) ->AnyCancellable

extension Publisher {

    /// Attaches a subscriber with closure-based behavior.
    ///
    /// Use ``Publisher/sink(receiveCompletion:receiveValue:)`` to observe values received by the publisher and process them using a closure you specify.
    ///
    /// In this example, a <doc://com.apple.documentation/documentation/Swift/Range> publisher publishes integers to a ``Publisher/sink(receiveCompletion:receiveValue:)`` operator’s `receiveValue` closure that prints them to the console. Upon completion the ``Publisher/sink(receiveCompletion:receiveValue:)`` operator’s `receiveCompletion` closure indicates the successful termination of the stream.
    ///
    ///     let myRange = (0...3)
    ///     cancellable = myRange.publisher
    ///         .sink(receiveCompletion: { print ("completion: \($0)") },
    ///               receiveValue: { print ("value: \($0)") })
    ///
    ///     // Prints:
    ///     //  value: 0
    ///     //  value: 1
    ///     //  value: 2
    ///     //  value: 3
    ///     //  completion: finished
    ///
    /// This method creates the subscriber and immediately requests an unlimited number of values, prior to returning the subscriber.
    /// The return value should be held, otherwise the stream will be canceled.
    ///
    /// - parameter receiveComplete: The closure to execute on completion.
    /// - parameter receiveValue: The closure to execute on receipt of a value.
    /// - Returns: A cancellable instance, which you use when you end assignment of the received value. Deallocation of the result will tear down the subscription stream.
    
    public func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
}

复制代码

SwiftUI 中几乎每个控件都可以充当订阅者。SwiftUI 中的View 协议定义了一个.onReceive(publisher)函数来使用视图作为订阅者

使用Demo

  1. 按钮UI
let cancellablePipeline = publishingSource 
                         .receive(on: RunLoop.main) 
                         .assign(to: \.isEnabled, on: yourButton) 
cancellablePipeline.cancel() 
复制代码
  1. 网络请求
  struct MyNetworkingError:Error {
      var invalidServerResponse:Int
    }
    var request = URLRequest(url: regularURL)
       request.allowsConstrainedNetworkAccess = false
    let network_publisher =  URLSession.shared.dataTaskPublisher(for: request)
      .tryCatch { error -> URLSession.DataTaskPublisher in
                 guard error.networkUnavailableReason == .constrained else {
                    throw error
                 }
                 return URLSession.shared.dataTaskPublisher(for: request)
      }
                
      .tryMap{ data,response -> Data in
                guard let httpResponse = response as? HTTPURLResponse,
                httpResponse.statusCode == 200 else {
                    throw MyNetworkingError(invalidServerResponse: 0)
                  }
                  return data
            }
                  
    .eraseToAnyPublisher()

复制代码
  • 示例2
static func randomImage() -> AnyPublisher<RandomImageResponse, GameError> {
    let url = URL(string: "https://api.unsplash.com/photos/random/?client_id=\(accessToken)")!

    let config = URLSessionConfiguration.default
    config.requestCachePolicy = .reloadIgnoringLocalCacheData
    config.urlCache = nil
    let session = URLSession(configuration: config)

    var urlRequest = URLRequest(url: url)
    urlRequest.addValue("Accept-Version", forHTTPHeaderField: "v1")

    return session.dataTaskPublisher(for: urlRequest)
      .tryMap { response -> Data in
        guard
          let httpURLResponse = response.response as? HTTPURLResponse,
          httpURLResponse.statusCode == 200
          else {
            throw GameError.statusCode
        }
        return response.data
      }
      .decode(type: RandomImageResponse.self, decoder: JSONDecoder())
      .mapError { GameError.map($0) }
      .eraseToAnyPublisher()
  }
复制代码
  1. Retry
let remoteDataPublisher = urlSession.dataTaskPublisher(for: self.URL!)
    .delay(for: DispatchQueue.SchedulerTimeType.Stride(integerLiteral: Int.random(in: 1..<5)), scheduler: backgroundQueue) 
    .retry(3) 
    .tryMap { data, response -> Data in 
        guard let httpResponse = response as? HTTPURLResponse,
            httpResponse.statusCode == 200 else {
                throw TestFailureCondition.invalidServerResponse
        }
        return data
    }
    .decode(type: PostmanEchoTimeStampCheckResponse.self, decoder: JSONDecoder())
    .subscribe(on: backgroundQueue)
    .eraseToAnyPublisher()

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享