SwiftUI:Publisher
本文内容
本文将介绍SwiftUI中的Publisher。
什么是Publisher
Publisher对象能够发布数据。作为一个协议,它的的签名是: Publisher<Output, Failure>
,其中Output
是Publisher发布的数据的类型,而Failure
是Publisher抛出的异常的类型。
有哪些Publisher
Publisher在SwiftUI中可谓无处不在,可以说它是SwiftUI的一个核心机制。在一个ObservableObject
对象中,每个标注了@Published
的属性,都会有一个对应的Publisher,其名称为:$原属性名
。比如下边的一个ObservableObject
class MyViewModel: ObservableObject {
@Published var foo: Int
}
其属性self.foo
对应的Publisher,就是self.$foo
。
其他的一些常见Publisher包括:
URLSession
的dataTaskPublisher
:发布从一个URL获取到的数据Timer
的publish(every:)
:定期发布当前日期和时间,类型为Date
NotificationCenter
的publisher(for:)
:订阅系统事件,在收到一个系统事件后发布通知。
订阅Publisher
要订阅Publisher,只需要调用Publisher的公有方法:sink(receiveCompletion:receiveValue:)
。该方法的两个参数均是闭包,当Publisher产生了异常或关闭了,就会触发receiveComplection
,而当Publisher发布了新的数据,就会触发receiveValue
。
如果这个Publisher不会抛出异常(类型为Publisher<Output, Never>
,也就是说Failure = Never
),那么你就可以用sink(receiveValue:)
重载方法,它省去了receiveCompletion
函数,让我们能写出更加简练的代码。再次强调,请注意一定要确保Publisher
不会抛出异常,否则使用该方法会导致编译错误。
下面的UIImageView
使用URLSession.shared.dataTaskPublisher(for:)
方法从某URL(可以是文件路径,也可以是网络地址)下载图片并将它展示出来。该方法返回的就是一个Publisher
,我们使用.sink
方法订阅这个Publisher
,好在下载完图片后,让Publisher
调用设置好的回调函数,将新图片展示在界面上。
class URLImageView: View {
private var imageFetchCancleable: AnyCancelable?
var body: some View {
Image(from: UIImage(data: data))
}
init(from url: String) {
downloadFrom(url: url)
}
func downloadDataFrom(url: String): Data {
URLSession.shared.dataTaskPublisher(for: url)
.sink(receiveCompletion: { result in
}, receiveValue: { [weak self] data in
self.data = data
})
}
}
其他常用方法
assign(to:on)
replaceError(with:)
map(_ transform:)
mapError(_ transform:)
receive(on:)
:决定订阅函数在哪个任务队列执行,默认是后台队列(Background Queue)。如果你想让Publisher别的任务队列,就需要用到这个方法。receive(on:Dispatch.main)
就指定在主队列中,通常是需要在订阅函数中更新UI是会这么做。
取消侦听
sink
函数会返回一个AnyCancelable
对象,我们可以调用其中的cancel()
方法来手动取消侦听:
let cancellable = publisher.sink({ value in ... })
cancellable.cancel()
不过,我们不必总是这么做,因为AnyCancellable
对象会在其销毁时,会自动帮我们调用cancel()
,取消侦听Publisher(doc)。
我们可以利用这一个特点,将AnyCancellable
定义为对象属性,这样只要对象还存活,那么这个对象就可以获取Publisher的最新数据:
class Program {
let cancellable: AnyCancellable?
func listenChanges() {
cancellable = createPublisherFoo()
.sink { error in
// 处理错误
} receiveValue: { value in
// 接收更新
}
}
}
onReceive
如果AnyCancellable
赋值给函数的一个局部变量,那么当离开变量的作用域后,侦听随即停止。这是一个坑:当我们调用完sink
函数就结束了,随即AnyCancellable
取消订阅Publisher,导致sink
里边的代码永远不会被执行。如果里边代码是用来更新试视图的,那么视图将不会更新。
class Program {
func listenChanges() {
let cancellable = createPublisherFoo()
.sink { error in
// 这个函数永远不会执行!
} receiveValue: { value in
// 这个函数也永远不会执行!
}
// 出了这个函数,订阅就没了!
}
}
View订阅Publisher
someView.onReceive { thingsPublisherPublishes in
// 实现与thingsPublisherPublishes相关的代码
}
当回调执行后,将会更新视图。