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 controlswhat
andwhen
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 有两种内置主题 CurrentValueSubject
和PassthroughSubject
它们的行为类似,不同之处在于 CurrentValueSubject
记住并需要初始状态,而 PassthroughSubject
则不需要。两者都会在.send()
被调用时向任何订阅者提供更新的值
订阅者 subscriber
Combine 内置了两个订阅者:Assign
和Sink
。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
- 按钮UI
let cancellablePipeline = publishingSource
.receive(on: RunLoop.main)
.assign(to: \.isEnabled, on: yourButton)
cancellablePipeline.cancel()
复制代码
- 网络请求
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()
}
复制代码
- 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()
复制代码