Publisher 是Combine框架里核心功能之一,我们通常翻译成:发布者、发布商,是一种拥有数据发布功能的协议,发布者是数据的提供者:
protocol Publisher //官方定义
如果你还不知道什么是Combine框架,请查看:http://www.55mx.com/ios/183.html
当订阅者请求数据时, 发布者有严格的返回值类型约定,并有一系列明确的可能会终止、完成的信号。当订阅者向发布者发出请求时,大多数的发布者会立即提供数据,但也有需要使用特殊机制才能启动发布的Publisher,如Timer(上一篇文章里示例代码里有演示额外启动数据流的机制)。
需要特殊机制启动的发布者有一个单独的机制,使其能够在订阅后返回数据。 这是由协议 ConnectablePublisher 来约定实现的。 遵循 ConnectablePublisher 的发布者将有一个额外的机制,在订阅者发出请求后才启动数据流。 这可能要求发布者单独的调用 .connect() 、 .autoconnect等来启动发布工作。
当我们使用发布者的方法/函数对数据发布时,Combine 不仅仅是需要定义结果的类型(Output),它还必须定义了我们如何处理失败(Failure)。 换句话说它不仅可以返回的发布的数据(类型),还返回可能发生的失败(虽然我们可以使用Naver忽略失败),所以发布者必须同时返回下面2个数据:
associatedtype Output//此发布者发布的值类型(必需有)
associatedtype Failure : Error//此发布者可能发布的错误类型。(必需有)
这2个数据,前者为发布的值,后者为可能在发布过程中出现的错误。我们每次发布的数据类型、可能出现的错误类型都可能会不一样,Swift是强类型语言,所以,这里我们使用泛型来处理这2个类型。如:<String, URLError>或者<Int, DecodingError>来描述发布的内容。
我们可以使用Combine 框架提供的几种类型之一来创建自己的发布者,而不是自己去实现Publisher协议。下面我们总结一下,Combine与其它swift框架提供的各种发布者的使用方式进行演示。首先,我们建立一个公共代码区域,使用run()函数调用:
import Foundation
import Combine //导入框架
final class CombinePublishersDemo {
private var cancellables = Set()//定义一个可取消的订阅者
func run() {
//放入下面对应的发布者函数即可
}
}
private func just() {
Just("55mx.com") //发布的内容
.sink { value in
print(#function, value)// 输出:just() 55mx.com
}
//store 方法可在 Cancellable 协议上调用,该协议明确设置为支持存储可用于取消管道的引用。
.store(in: &cancellables)
}
Just常被用在错误处理中,在捕获异常后发送备用值(如:图片下载失败了,使用默认图片代替)。 示例代码:
func justWithError() {
// 使用 Fail 发送失败
Fail(error: NSError(domain: "", code: 0, userInfo: nil))
.catch { _ in
return Just("错误信息:域名为空")// 捕获错误,返回 Just("备用值")
}
.sink { value in
print(#function, value)// 输出:justWithError() 错误信息:域名为空
}
.store(in: &cancellables)
}
前面我们用过Just,其数据的发布和订阅是同步行为。如果希望数据的发布和订阅是异步的,可以使用Future。Future可以创建一个接收未来数据与事件的 Publisher。Future定义如下:
func future() {
Future<String, Never> { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
promise(.success("55mx.com")) // 延时1秒发布”55mx.com“
}
}
.sink { value in
print(#function, value)// 输出:future() 55mx.com
}
.store(in: &cancellables)
}
注意:这里的Future通过DispatchQueue完成的异步操作,Futere与上面Just不同在于:前者可以执行闭包调用一些功能后再发布,而后者是直接发布单个数据。如果你想使用真的异步功能,请使用下面的Deferred。
Future 更常见的用法是将其作为一个任务函数的返回值,让具体任务的执行代码与订阅代码分离:
private func bigTask() -> Future<Int, Error> {
return Future() { promise in
sleep(1)// 休眠1秒(模拟耗时操作)
guard Bool.random() else {
promise(.failure(NSError(domain: "55mx.com", code: -1, userInfo: [NSLocalizedDescriptionKey: "任务失败"])))
return
}
promise(.success(3))
}
}
//因为上面随机返回一个布尔值,所以下面执行结果会变化
func futureWithBigTask() {
bigTask()
.subscribe(on: DispatchQueue.global())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print(#function, "完成")// 输出:futureWithBigTask() 完成
case .failure(let error):
// 输出:futureWithBigTask() Error Domain=55mx.com Code=-1 "任务失败"...}
print(#function, error)
}
}, receiveValue: { value in
print(#function, value)// 输出:futureWithBigTask() 3
})
.store(in: &cancellables)
}
输出内容由 Bool.random() 决定,因为它随机返回了一个布尔值。
func deferred() {
let deferredPublisher = Deferred<AnyPublisher<Bool, Error>> {
print(Date(), "我被订阅了")// 在订阅之后才会执行
return Future<Bool, Error> { promise in
promise(.success(true))
}.eraseToAnyPublisher()
}.eraseToAnyPublisher()
print(Date(), "延时创建")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {// 延迟1秒后进行订阅
deferredPublisher
.sink(receiveCompletion: { completion in
print(Date(), "延时接收状态:", completion)
}, receiveValue: { value in
print(Date(), "延时接收到的值:", value)
})
.store(in: &self.cancellables)
}
}
执行上面的函数,输出内容如下(请注意观察输出的时间,延时1秒):
2021-12-20 19:30:35 +0000 延时创建
2021-12-20 19:30:36 +0000 我被订阅了
2021-12-20 19:30:36 +0000 延时接收到的值: true
2021-12-20 19:30:36 +0000 延时接收状态: finished
在前期学习SwiftUI里我们将到了MVVM开发模式与数据流向,里面的@Published 和 @ObservedObject 属性包装,其实也由 Combine 提供,并含蓄地创建了一个发布者,用来支持它的声明式 UI 的机制。
关于MVVM开发模式和数据流请查看:http://www.55mx.com/ios/147.html
除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/184
《【Combine入门】一文搞懂Combine框架里的发布者(Publisher)》的网友评论(0)