Swift Combine 开发小册 – 2. 基础用法

A. 导入Combine

在任何想要运用Combine的Swift文件中,能够在文件顶部添加以下导入语句:

import Combine

这将使得Combine中的所有类、结构体和函数在该文件中可用。

需求留意的是,Combine只能在iOS 13.0+ / macOS 10.15+中运用。

B. 发布者和订阅者

  • 了解Combine中发布者和订阅者的效果
  • 创立一个发布者以宣布数据
  • 创立一个订阅者以接纳来自发布者的数据
  • 衔接发布者和订阅者以创立数据流

1. 了解发布者和订阅者

在Combine中,发布者和订阅者在促进应用程序不同部分之间的数据流时起着至关重要的效果。发布者是一种目标,它会随着时刻推移宣布一系列值,而订阅者则是接纳和处理这些值的目标。

发布者负责定义它将宣布的值序列,并在新值可用时告诉其订阅者。发布者能够宣布单个值、一系列值或过错,并且在没有更多值要宣布时也能够完结值序列。

另一方面,订阅者负责接纳和处理由发布者宣布的值。它们能够在每个值宣布时进行处理,或随着时刻的推移累积值并将它们作为一组进行处理。订阅者还能够处理由发布者宣布的过错和完结告诉。

为了让订阅者从发布者接纳值,它必须首先树立与发布者的衔接。这种衔接是运用subscribe办法树立的,该办法接受一个闭包,定义了订阅者的行为。

2. 创立一个发布者

有两种类型的发布者,一种是直接创立,另一种是经过操作第一个发布者创立的,我们称之为操作符:

  • 直接创立的发布者:
    • 运用Just发布者宣布单个值。
    • 运用Future发布者在将来的某个时刻宣布单个值。
    • 运用PassthroughSubject发布者依据用户事情或其他触发器宣布值。
    • 运用CurrentValueSubject发布者宣布值并保存最近发布的值的缓冲区。
  • 运用mapfilter或其他操作符从头发布原始的发布者。
  • 经过完结Publisher协议创立自定义发布者。

下面来看看如何创立发布者:

  • Just
let publisher = Just(42)

这创立了一个发布者,它发布了一个单一的值(42),然后立即完毕。

  • Future
let publisher = Future<String, Error> { promise in
    // Perform an asynchronous task
    DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
        // When the task completes, fulfill the promise
        promise(.success("Hello, world!"))
    }
}

这创立了一个发布者,履行异步使命并在使命完结时宣布单一的值(”Hello, world!”)。假如使命遇到过错,发布者会宣布一个过错。

还能够运用 async-await 语法来获取 Future 中的值(或过错):

do {
 let value = try await publisher.value
 print("Received value: \(value)")
} catch {
print("Error occurred with info: \(error.localizedDescription)")
}
  • PassthroughSubject
class MyViewModel {
    private let usernameSubject = PassthroughSubject<String, Never>()
    var usernamePublisher: AnyPublisher<String, Never> {
        return usernameSubject.eraseToAnyPublisher()
    }
    func updateUsername(_ username: String) {
        usernameSubject.send(username)
    }
}

这创立了一个自定义发布者(usernameSubject),每逢调用updateUsername办法时,它就会宣布一个字符串值。发布者被类型擦除为AnyPublisher,以便能够安全地暴露给视图模型的公共接口。

  • CurrentValueSubject
// Define a CurrentValueSubject with an initial value of 0
let subject = CurrentValueSubject<Int, Never>(0)
// Subscribe to the subject and print out each emitted value
let cancellable = subject.sink { value in
    print("Value: \(value)")
}
// Update the value of the subject
subject.send(1)
subject.send(2)
// Print out the current value of the subject
print("Current value: \(subject.value)")
// Cancel the subscription
cancellable.cancel()
/* output:
Value: 0
Value: 1
Value: 2
Current value: 2
*/
  • 何时运用Just、Future、PassthroughSubject、CurrentValueSubject:

    • Just:当你想要创立一个宣布单一值然后立即完结的发布者时,能够运用Just。当你想要创立一个宣布常量值或在订阅时能够计算出值的发布者时,这是十分有用的。Just发布者也适用于用Publishers.Catch替换值时,Just总是会产生一个值而不会出错。
    • Future:当你想要创立一个在未来某个时刻宣布单一值的发布者时,能够运用Future。当你想要履行一些异步作业,例如网络请求或磁盘I/O,并在作业完结时宣布值时,这是十分有用的。
    • PassthroughSubject:当你想要创立一个答应你手意向其订阅者发送值的发布者时,能够运用PassthroughSubject。当你想要创立一个根据某些外部事情或用户交互宣布值的自定义发布者时,这是十分有用的。
    • CurrentValueSubject:当你想要创立一个将单个值封装起来,并在值发生变化时向其订阅者发布它的发布者时,能够运用CurrentValueSubject。当你想要创立一个表示可变状况的发布者时,如用户界面元素的状况或同享数据模型,这是十分有用的。

    一般,你应该选择最适合你特定用例的发布者类型。例如,假如你需求履行一些异步作业,然后宣布一个值,你将运用Future。假如你需求创立一个根据用户交互宣布值的自定义发布者,你将运用PassthroughSubject。假如你需求表示能够由应用程序的多个部分更新的可变状况,则将运用CurrentValueSubject

3. 创立一个订阅者

在Combine中有两种类型的订阅者:

  • sink订阅者,答应你经过闭包处理宣布的值、过错和完结告诉。
  • assign订阅者,答应你将宣布的值绑定到目标上的某个特点。
  • 你也能够经过完结Subscriber协议来创立自定义订阅者。

让我们看一些比如:

  • sink
let publisher = Just("Hello, world!")
publisher
    .sink(receiveCompletion: { print("Received completion: \($0)") },
          receiveValue: { print("Received value: \($0)") })

这创立了一个运用sink办法的订阅者,它打印发布者宣布的任何值(”Hello, world!”),并打印任何完结事情(在本例中是一个没有过错的完结)。

  • assign
class MyViewModel {
    let username = CurrentValueSubject<String, Never>("")
    var usernameLabelText: String {
        didSet {
			print(usernameLabelText)
        }
    }
    private var cancellables = Set<AnyCancellable>()
    init() {
        username
            .assign(to: \.usernameLabelText, on: self)
            .store(in: &cancellables)
    }
	deinit {
        cancellables.forEach { $0.cancel() }
    }
}

这创立了一个运用assign办法的订阅者,将发布者宣布的值(username)绑定到订阅者(usernameLabelText)上的特点。在这个比如中,订阅者是一个视图模型,被绑定的特点(usernameLabelText)是一个在用户界面中显示的字符串。订阅者还被添加到一个cancellables集合中,以确保在不再需求视图模型时将其销毁。

4. 衔接发布者和订阅者

- 运用`subscribe`办法将发布者衔接到订阅者。
- 运用`eraseToAnyPublisher`办法对发布者进行类型擦除。

以下是一些比如:

  • 运用自定义订阅者的subscribe
let publisher = Just("Hello, world!")
class MySubscriber: Subscriber {
    typealias Input = String
    typealias Failure = Never
    func receive(subscription: Subscription) {
        subscription.request(.max(1))
    }
    func receive(_ input: String) -> Subscribers.Demand {
        print("Received value: \(input)")
        return .none
    }
    func receive(completion: Subscribers.Completion<Never>) {
        print("Received completion: \(completion)")
    }
}
publisher.subscribe(MySubscriber())

这运用subscribe办法将发布者(publisher)与一个自定义订阅者衔接。在这个比如中,订阅者是一个完结了Subscriber协议的自定义类(MySubscriber)。订阅者从发布者接纳到一个字符串值并将其打印到控制台。订阅者还完结了receiveCompletion办法来打印发布者宣布的任何完结事情。

  • 对已知输出和失利类型的发布者进行类型擦除:
let publisher = Just("Hello, world!")
let anyPublisher = publisher.eraseToAnyPublisher()

这创立了一个宣布字符串值的发布者(publisher),然后运用eraseToAnyPublisher办法将其类型擦除为一个AnyPublisher<String, Never>

  • 对用于公共API的发布者进行类型擦除:
class MyViewModel {
    private let usernameSubject = PassthroughSubject<String, Never>()
    var usernamePublisher: AnyPublisher<String, Never> {
        return usernameSubject.eraseToAnyPublisher()
    }
    func updateUsername(_ username: String) {
        usernameSubject.send(username)
    }
}

这创立了一个自定义发布者(usernameSubject),每逢调用updateUsername办法时就会宣布一个字符串值。运用eraseToAnyPublisher办法将发布者类型擦除为一个AnyPublisher,以便能够安全地暴露给视图模型的公共接口。这使得视图模型的客户端能够订阅该发布者,而无需知道具体运用的发布者类型。

经过了解如安在Swift Combine中运用发布者和订阅者,你能够创立强大的数据流,轻松处理异步和事情驱动的编程使命。鄙人一节中,我们将介绍如何运用操作符来转化和操作Combine中的数据流。

文章首发发布地址:Github

Photo by Maxim Berg on Unsplash