75142913在线留言
【Combine入门】一文搞懂Combine框架里的发布者(Publisher)_IOS开发_网络人

【Combine入门】一文搞懂Combine框架里的发布者(Publisher)

Kwok 发表于:2021-12-20 16:20:43 点击:62 评论: 0

PublisherCombine框架里核心功能之一,我们通常翻译成:发布者、发布商,是一种拥有数据发布功能的协议,发布者是数据的提供者:

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() {
        //放入下面对应的发布者函数即可
    }
}

一、Combine 提供的发布者: 

  • Just 向每个订阅者只(同步)发送单个值,然后结束。它的失败类型为 Never(也就是不能失败)。
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)
}
  • Future 使用一个闭包来进行初始化,最终这个闭包将执行传入的一个闭包参数(promise)来发送单个值或者失败。

前面我们用过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() 决定,因为它随机返回了一个布尔值。

  • Deferred 使用一个生成发布者的闭包来完成初始化,这个闭包会在订阅者执行订阅操作时才执行(触发式异步)。
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

 

  • Empty
  • Sequence
  • Fail
  • Record
  • Share
  • Multicast
  • ObservableObject
  • @Published

二、Foundation 提供的发布者:

  • URLSession.dataTask
  • KVO(Key-Value Observing)实例中的 .publisher
  • NotificationCenter
  • Timer
  • Result 

 

三、 SwiftUI 使用的属性包装

在前期学习SwiftUI里我们将到了MVVM开发模式与数据流向,里面的@Published 和 @ObservedObject 属性包装,其实也由 Combine 提供,并含蓄地创建了一个发布者,用来支持它的声明式 UI 的机制。

关于MVVM开发模式和数据流请查看:http://www.55mx.com/ios/147.html

除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/184
标签:Combine发布者PublisherKwok最后编辑于:2021-12-20 19:20:34
0
感谢打赏!

《【Combine入门】一文搞懂Combine框架里的发布者(Publisher)》的网友评论(0)

本站推荐阅读

热门点击文章