在这个章节里,我们将学习如何使用不同类型的操作符来处理从发布者发送出来的数据,它们是Swift Combine中最强大的工具,但是可能需要一点时间去学习。所以,坐稳了,让我们开始吧。
操作符是在Swift Combine中处理数据流的重要工具。它们是一些方法集合,可以转换、过滤和组合数据流,它们返回新的发布者,让你可以将它们串联起来创建复杂的数据转换,这个过程也被称为重新发布。
我们将这些操作符分为四类:
- 过滤和转换数据
- 合并多个数据流
- 控制时序
- 错误处理和调试
A. 过滤和转换数据
在本节中,我们将向您展示如何使用这些操作符来过滤和转换数据流,并在实际应用中将它们串联起来:
- filter
- map
- reduce
- flatMap
- scan
- encode
- decode
filter
在Swift Combine中,filter
操作符用于根据提供的条件选择性地从发布者中发出值。filter
操作符采用返回布尔值的闭包,并将其应用于发布者发出的每个值。仅当闭包返回true
时,才将值转发到下游的订阅者。
以下是使用filter
操作符从PassthroughSubject
发布者中仅发出偶数整数的示例:
let subject = PassthroughSubject<Int, Never>()
let subscription = subject
.filter { $0 % 2 == 0 } // 只发出偶数值
.sink { print($0) }
subject.send(1) // 奇数不会发出
subject.send(2) // 偶数会发出
subject.send(3) // 奇数不会发出
subject.send(4) // 偶数会发出
// 输出: 2 4
在这个例子中,我们创建了一个发出整数的PassthroughSubject
发布者。我们使用filter
操作符只将偶数整数下发给订阅者。当我们向发布者发送整数时,订阅者只会打印偶数整数。
map
在Swift Combine中,map
操作符用于转换发布者发出的值。map
操作符采用转换发布者发出的每个值的闭包,并将转换后的值向下游发出给订阅者。
以下是使用map
操作符将发出整数的PassthroughSubject
发布者转换为发出这些整数的平方值的发布者的示例:
let subject = PassthroughSubject<Int, Never>()
let subscription = subject
.map { $0 * $0 } // 计算平方
.sink { print($0) }
subject.send(1)
subject.send(2)
subject.send(3)
// 输出: 1 4 9
在这个例子中,我们创建了一个发出整数的PassthroughSubject
发布者。我们使用map
操作符将每个发出的整数转换为其平方值,向下游发出给订阅者。当我们向发布者发送整数时,订阅者打印这些整数的平方值。
reduce
在Swift Combine中,reduce
操作符用于将发布者发出的值累加到单个值中。它需要一个初始值和一个闭包,该闭包将先前累加的值与发布者发出的当前值结合起来。闭包的结果向下游发出给订阅者,并成为下一个发射的新累积值。
reduce
操作符要求发布者在发出最终累加值之前完成。这意味着只有在发布者完成发出值后,订阅者才会收到最终结果。
以下是使用reduce
操作符累加由PassthroughSubject
发布者发出的所有整数的总和的示例:
let subject = PassthroughSubject<Int, Never>()
let subscription = subject
.reduce(0, +) // 累加所有值
.sink { print($0) }
subject.send(1)
subject.send(2)
subject.send(3)
subject.send(completion: .finished)
// 输出: 6
在这个例子中,我们创建了一个发出整数的PassthroughSubject
发布者。我们使用reduce
操作符将所有发出的整数的总和累加到向下游发出给订阅者的单个值中。当我们向发布者发送整数时,订阅者在发布者完成发出值后打印这些整数的累加和。
reduce
操作符用于将发布者发出的值累加到单个值中,但它要求发布者在发出最终结果之前完成。它可以与其他操作符结合使用,以在Swift的响应式编程中创建更复杂的处理管道。
flatMap
在Swift Combine中,flatMap
操作符用于将发布者发出的值转换为新的发布者,然后将这些发布者展开为单个值的流。
flatMap
操作符采用一个闭包,该闭包返回每个原始发布者发出的发布者。操作符订阅每个内部发布者,并将它们的值向下游转发给订阅者。内部发布者被展平为单个值的流,这些值按接收顺序发出。
以下是使用flatMap
操作符展开发出整数数组的PassthroughSubject
发布者的示例:
let subject = PassthroughSubject<[Int], Never>()
let subscription = subject
.flatMap { array in
return array.map({ $0 + 1 }).publisher // 变换每个元素并转换为发布者
}
.sink { print($0) }
subject.send([1, 2, 3])
subject.send([4, 5, 6])
// 输出: 2 3 4 5 6 7
在这个例子中,我们创建了一个发出整数数组的PassthroughSubject
发布者。我们使用flatMap
操作符将发出的数组中的每个整数加1进行转换。然后,我们使用publisher
属性将每个转换后的整数转换为发布者,并将结果发布者展开为单个值的流。当我们向发布者发送整数数组时,订阅者按接收顺序打印展开的整数流。
flatMap
操作符用于将发布者发出的值转换为新的发布者,并将它们展开为单个值的流。它可以与其他操作符结合使用,以在Swift的响应式编程中创建更复杂的处理管道。
scan
在Swift Combine中,scan
操作符用于将发布者发出的值累加到中间结果序列中。它类似于reduce
操作符,但不是发出最终累加值,而是在计算每个中间结果时将其发出。
scan
操作符需要一个初始值和一个闭包,该闭包将先前累加的值与发布者发出的当前值结合起来。闭包的结果将作为中间结果向下游发出给订阅者,并成为下一个发射的新累积值。
以下是使用scan
操作符累加由PassthroughSubject
发布者发出的整数的运行总和的示例:
let subject = PassthroughSubject<Int, Never>()
let subscription = subject
.scan(0, +) // 累加所有发出的数
.sink { print($0) }
subject.send(1)
subject.send(2)
subject.send(3)
// 输出: 1 3 6
在这个例子中,我们创建了一个发出整数的PassthroughSubject
发布者。我们使用scan
操作符将所有发出的整数的运行总和累加到向下游发出给订阅者的中间结果序列中。当我们向发布者发送整数时,订阅者在每个值被发出后打印运行总和的中间结果。
scan
操作符用于将发布者发出的值累加到中间结果序列中。它可以与其他操作符结合使用,以在Swift的响应式编程中创建更复杂的处理管道。
encode
在Swift Combine中,encode
操作符用于将发布者发出的值编码为指定格式,例如JSON或XML。
encode
操作符需要一个遵循TopLevelEncoder
协议的编码器实例,并返回一个发布者,该发布者使用提供的编码器将由上游发布者发出的值编码为指定格式,这些值需要符合Encodable
协议。
以下是使用encode
操作符将自定义结构体编码为JSON的示例:
struct Person: Codable {
var name: String
var age: Int
}
let person = Person(name: "John", age: 30)
let publisher = Just(person)
.encode(encoder: JSONEncoder()) // 将 person 编码为 JSON Data
.map { String(data: $0, encoding: .utf8) } // 将 Data 转换为 String
.sink(receiveCompletion: { print($0) }, receiveValue: { print($0 ?? "") })
// 输出: {"name":"John","age":30}
在这个例子中,我们使用Just
发布者发出一个自定义的Person
结构体。然后,我们使用encode
操作符将Person
结构体编码为JSON数据,使用JSONEncoder
。为了将结果Data
对象转换为String
,我们使用map
操作符将String(data:encoding:)
初始化器应用于每个发出的Data
对象。最后,生成的String
被向下游发出给订阅者,订阅者打印编码的JSON字符串。
encode
操作符用于将发布者发出的值编码为指定格式以进行进一步处理或通信。它可以与其他操作符结合使用,以在Swift的响应式编程中创建更复杂的处理管道。
decode
在Swift Combine中,decode
操作符用于将由发布者发出的特定格式(例如JSON或XML)的数据流解码为符合Decodable
协议的Swift类型的实例。
decode
操作符需要一个遵循TopLevelDecoder
协议的解码器实例,并返回一个发布者,该发布者使用提供的解码器对上游发布者发出的值进行解码。
以下是使用decode
操作符将JSON数据流解码为自定义Person
结构体实例的示例:
struct Person: Codable {
var name: String
var age: Int
}
let jsonData = """
{"name": "John", "age": 30}
""".data(using: .utf8)!
let publisher = Just(jsonData)
.decode(type: Person.self, decoder: JSONDecoder()) // 解码为 Person 类型
.sink(receiveCompletion: { print($0) }, receiveValue: { print($0) })
// 输出: Person(name: "John", age: 30)
在这个例子中,我们创建一个Just
发布者,该发布者发出一个JSON数据流。然后,我们使用decode
操作符将JSON数据解码为自定义Person
结构体的实例,使用JSONDecoder
。生成的Person
对象被向下游发出给订阅者,订阅者打印解码后的值。
decode
操作符用于将发布者发出的数据流解码为自定义类型的实例,以进行进一步处理或在应用程序中使用。它可以与其他操作符结合使用,以在Swift的响应式编程中创建更复杂的处理管道。
操作符链接
可以将操作符链接在一起,以创建更复杂的数据转换,每个步骤的转换都会创建一个新的发布者。现在,让我们进行一些操作符链接。
- 链接
map
和filter
:
let publisher = PassthroughSubject<Int, Never>()
var cancellables = Set<AnyCancellable>()
publisher
.map { value in
return value * 2
}
.filter { value in
return value % 3 == 0
}
.sink { value in
print("Received value: \(value)")
}
.store(in: &cancellables)
publisher.send(1)
publisher.send(2)
publisher.send(3)
publisher.send(4)
publisher.send(5)
// 输出: Received value: 6
这将创建一个发布者(publisher
),它会发出整数值,然后链接map
和filter
操作符。map
操作符会将发出的值加倍,filter
操作符仅传递可被3整除的值。然后订阅者接收到过滤和映射的值(6
),并将它们打印到控制台。
- 链接
flatMap
和filter
:
let publisher = PassthroughSubject<String, Never>()
var cancellables = Set<AnyCancellable>()
publisher
.flatMap { value in
return Just(value.count)
}
.filter { count in
return count % 2 == 0
}
.sink { count in
print("Received value with even count: \(count)")
}
.store(in: &cancellables)
publisher.send("Hello")
publisher.send("World!")
publisher.send("Swift")
// 输出: Received value with even count: 6
这将创建一个发布者(publisher
),它会发出字符串值,然后链接flatMap
和filter
操作符。flatMap
操作符将每个字符串值转换为一个发出该字符串计数的新发布者。filter
操作符仅传递计数为偶数的值。然后订阅者接收到过滤和映射的值(6
),并将它们打印到控制台。
- 链接
map
、filter
和reduce
:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let result = numbers.publisher
.map { $0 * 2 } // Multiply each number by 2
.filter { $0 % 3 == 0 } // Filter out any numbers that aren't divisible by 3
.reduce(0, +) // Sum all of the remaining numbers
.sink { print($0) }
print(result) // 输出: 36
在这个例子中,我们有一个从1到10的数字数组。我们使用publisher
属性从该数组创建一个发布者。我们然后链接三个运算符:
map
:我们使用map
运算符将每个数字乘以2,以便我们最终得到一个数字从2到20的数组。filter
:我们使用filter
运算符删除任何不能被3整除的数字,以便我们最终得到一个数字6、12和18的数组。reduce
:最后,我们使用reduce
运算符对所有剩余的数字进行求和,从0开始。结果是36。
B. 合并多个数据流
在某些情况下,您可能需要在应用程序中组合多个数据流。这可能对于合并用户输入和来自服务器的数据或基于多个数据源更新UI组件非常有用。Swift Combine提供了各种运算符来帮助您组合多个数据流。
zip
在Swift Combine中,zip
运算符用于将两个发布者组合成一个新的发布者,该发布者发出包含每个输入发布者的最新值的元组。
返回的发布者等待两个发布者都发出事件,然后将每个发布者的最旧未消耗事件一起作为元组传递给订阅者。如果任何一个上游发布者成功完成或因错误而失败,则zip发布者也会如此。
以下是使用zip
运算符将接收用户输入值的两个发布者组合的示例:
let username = PassthroughSubject<String, Never>()
let password = PassthroughSubject<String, Never>()
let credentials = Publishers.Zip(username, password)
.map { (username: $0, password: $1) }
let cancellable = credentials
.sink(receiveCompletion: { print($0) },
receiveValue: { print("Username: \($0.username), Password: \($0.password)") })
username.send("john.doe@example.com")
password.send("password123")
password.send("password456")
username.send("mike.gould@example.com")
username.send(completion: .finished)
/*
Output:
Username: john.doe@example.com, Password: password123
Username: mike.gould@example.com, Password: password456
finished
*/
在这个例子中,我们创建了两个PassthroughSubject
发布者,username
和password
,用于接收用户输入的值。我们使用Zip
操作符将这两个发布者组合成一个新的发布者credentials
,它发出包含每个输入发布者的最新值的元组。然后我们使用map
操作符将元组转换为更有意义的类型,以适应我们的用例。
最后,我们使用sink
操作符订阅credentials
发布者并打印它发出的最新值。然后,我们向username
和password
发布者分别发送两对值,并观察sink
操作符的输出。
这个例子的输出是两个元组,分别是发送到username
和password
发布者的用户名和密码值。然后,username
发送了完成事件,导致zipped发布者也完成。
类似地,您可以使用Publishers.Zip3
和Publishers.Zip4
来 zip 三个或四个发布者(没有Zip5)。
merge
在 Swift Combine 中,merge
操作符用于将多个发布者合并为一个单一的发布者,以非确定性的顺序发出所有输入发布者的值。
merge
操作符接受可变数量的输入发布者,并返回一个新的发布者,该发布者会在接收到所有输入发布者的值时发出这些值。每当一个输入发布者发出新值时,merge
操作符就会将该值下游发出。
下面是一个将两个 PassthroughSubject
发布者使用 merge
方法合并在一起并发出其值的示例:
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<Int, Never>()
let mergedPublisher = publisher1
.merge(with: publisher2)
.sink { print($0) }
publisher1.send(1)
publisher2.send(2)
publisher1.send(3)
publisher2.send(4)
// Output: 1, 2, 3, 4
在这个例子中,我们创建了两个 PassthroughSubject
发布者 publisher1
和 publisher2
,它们都发出整数值。我们使用 merge
方法将这两个发布者合并成一个名为 mergedPublisher
的新发布者,它会按接收到的顺序从两个发布者中发出值。然后我们使用 sink
运算符订阅 mergedPublisher
并打印它发出的最新值。
最后,我们向 publisher1
和 publisher2
分别发送四个整数值,并观察 sink
运算符的输出。正如我们所预期的,输出包括按发送顺序发送的所有四个整数值。
您可以合并任意数量的发布者,merge
运算符在您有多个发出相关数据流的发布者需要一起处理时非常有用。
combineLatest
在 Swift Combine 中,combineLatest
运算符用于将多个发布者的最新值组合成一个新的发布者。每当任何一个输入发布者发出新值时,combineLatest
运算符就会将每个发布者的最新值组合并作为元组向下游发送组合结果。
以下是使用 combineLatest
运算符将两个发出整数值的发布者组合的示例:
let numbers1 = PassthroughSubject<Int, Never>()
let numbers2 = PassthroughSubject<Int, Never>()
let combinedPublisher = numbers1
.combineLatest(numbers2)
.sink { print("Numbers: \($0)") }
numbers1.send(1)
numbers2.send(2)
numbers1.send(3)
numbers2.send(4)
// Output: Numbers: (1, 2), Numbers: (3, 2), Numbers: (3, 4)
在这个例子中,我们创建了两个 PassthroughSubject
发布者,分别是 numbers1
和 numbers2
,它们都会发出整数值。我们使用 combineLatest
操作符将两个发布者的最新值组合起来,并将组合后的结果作为元组向下游发布。然后我们使用 sink
操作符订阅 combinedPublisher
并打印它发出的最新组合值。
最后,我们向 numbers1
和 numbers2
分别发送了四个整数值,并观察 sink
操作符的输出。正如预期的那样,输出包含 numbers1
和 numbers2
的最新组合值,每当其中任何一个发布者发出新值时都会更新。
combineLatest
操作符可以在需要将多个发布者的最新值组合在一起并一起处理它们的情况下发挥作用。例如,它可以用于在任何输入数据源更改时更新用户界面。
C. 控制时序
measureInterval
该运算符测量了发布者的每次发出动作之间的时间间隔,并将该间隔作为 TimeInterval
值进行再次发布。它非常适用于计时相关任务,例如测量游戏或动画的帧率。
var cancellables = Set<AnyCancellable>()
let publisher = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
publisher
.measureInterval(using: DispatchQueue.global())
.sink { interval in
print("Received interval: \(interval)")
}
.store(in: &cancellables)
这个示例创建了一个计时器发布者(publisher
),每隔一秒钟发出一个值,并使用measureInterval
运算符测量每次发出之间的时间间隔。订阅者接收每个发出之间的时间间隔,并将其打印到控制台。
debounce
这个操作符会推迟一个发布者(Publisher)发出值的时间,确保在指定的时间内只发出最后一个值,在这之前的值都将被丢弃。这对于过滤噪声或重复值非常有用,或者用于处理用户输入,因为它们可能会在短时间内生成多个事件。
let publisher = PassthroughSubject<String, Never>()
publisher
.debounce(for: .seconds(1), scheduler: DispatchQueue.main)
.sink { value in
print("Received value: \(value)")
}
.store(in: &cancellables)
publisher.send("H")
publisher.send("He")
publisher.send("Hel")
publisher.send("Hell")
publisher.send("Hello")
try? await Task.sleep(nanoseconds: 1000_000_000)
publisher.send("World")
try? await Task.sleep(nanoseconds: 1000_000_000)
publisher.send(completion: .finished)
try? await Task.sleep(nanoseconds: 1000_000_000)
/*
output:
Received value: Hello
Received value: World
*/
这个例子中,我们创建了一个发布者(publisher),它发出字符串类型的值,然后使用防抖操作符(debounce operator)只传递在一秒内没有被另一个值跟随的值。订阅者会接收到这些经过防抖后的值(Hello 和 World),并将它们打印到控制台上。这个操作符通常用于过滤掉噪声或重复的值,或者处理用户输入时可能在短时间内生成多个事件的情况。
delay
这个操作符可以让一个发布者(publisher)延迟一段指定的时间再发送值。这对于模拟网络延迟或其他基于时间的效果很有用。
var cancellables = Set<AnyCancellable>()
let publisher = Just("Hello, world!")
publisher
.delay(for: .seconds(2), scheduler: DispatchQueue.main)
.sink { value in
print("Received value: \(value)")
}
.store(in: &cancellables)
这个例子中,我们创建了一个只会发出一个字符串值 (Hello, world!
) 的 publisher (publisher
),接着使用 delay
运算符来延迟这个值的发出,延迟时间为两秒钟。然后订阅者会接收到这个延迟后的值 (Hello, world!
) 并将其打印到控制台上。这个运算符通常用于模拟网络延迟或者其他需要按时间延迟的效果。
throttle
这个操作符限制了一个发布者每隔一段指定的时间间隔只能发出最新或最早的值。这对于处理用户输入或其他快速变化的数据流非常有用。
var cancellables = Set<AnyCancellable>()
let publisher = PassthroughSubject<String, Never>()
publisher
.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
.sink { value in
print("Received value: \(value)")
}
.store(in: &cancellables)
publisher.send("H")
publisher.send("He")
publisher.send("Hel")
publisher.send("Hell")
publisher.send("Hello")
publisher.send("World")
publisher.send(completion: .finished)
这里创建了一个发布者(publisher
),它会不断地发出字符串类型的值,然后使用 throttle
操作符,仅在指定时间内没有后续值时,才将值传递给下游订阅者。latest
参数设置为 true
,这意味着在时间窗口内最新的值将被传递给订阅者。订阅者最后会收到被节流后的值(World
),并将其打印到控制台上。如果将 latest
改为 false
,则订阅者会收到最早的值 H
。
timeout
timeout
运算符在一定时间内没有接收到任何数据时,会将 Publisher 的流转化为错误流,并在订阅者处触发错误事件。这个运算符非常适合处理网络请求或其他时间敏感的任务,因为如果在规定的时间内没有收到回应,则可以中断任务并给出错误提示。
enum TimeoutError: Error {
case timeout
}
var cancellables = Set<AnyCancellable>()
let publisher = PassthroughSubject<String, TimeoutError>()
publisher
.timeout(.seconds(2), scheduler: DispatchQueue.main, customError: { () -> TimeoutError in
return TimeoutError.timeout
})
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("Finished")
case .failure(let error):
print("Error: \(error)")
}
},
receiveValue: { value in
print("Received value: \(value)")
}
)
.store(in: &cancellables)
publisher.send("Hello")
publisher.send("World")
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
publisher.send("Timeout")
}
这段代码创建了一个发布器(publisher
),它会发出一些字符串类型的值,并使用timeout
操作符来设置每个值的发射时间限制为2秒。如果一个值在时间限制内未被发出,发布器会向订阅者发送一个自定义的错误(TimeoutError
)。订阅者会接收到超时的值和错误,并将它们打印到控制台上。
当代码执行时,它会在控制台上输出以下内容:
Received value: Hello
Received value: World
Error: timeout
D. 错误处理和调试
在使用响应式编程和异步数据流时,处理错误并有效地调试代码非常重要。Swift Combine 提供了各种用于错误处理和调试的工具。以下是一些可用的技巧:
catch
catch
操作符允许您处理由发布者发出的错误。- 当发出错误时,
catch
操作符可以用新值替换错误或发出新错误。 - 使用此操作符处理错误并从中恢复。
下面是一个示例,演示如何使用catch
操作符处理错误:
enum MyError: Error {
case invalidValue
}
let numbers: [Any] = [1, 2, 3, 4, 5, "Six", 7, 8, 9, 10]
let publisher = numbers.publisher
let filteredPublisher = publisher.tryMap { value -> Int in
guard let intValue = value as? Int else {
throw MyError.invalidValue
}
return intValue
}.catch { error in
return Just(0)
}
filteredPublisher.sink { print($0) }
在这个例子中,我们创建了一个发布者,它会发出整数和一个字符串值。我们使用 tryMap
运算符将每个值转换为整数,如果值不是整数则抛出错误,并且当 map 的闭包抛出错误时,发布将停止发布。我们使用 catch
运算符将任何错误替换为默认值 0。当我们使用 sink
方法订阅 filteredPublisher
时,我们会打印出它所发出的每个值,包括替换的值,最终还会打印出错误的发射值。
1
2
3
4
5
0
catch
运算符可以用于在发布者链中优雅地处理错误,并通过返回一个新的发布者来从错误中恢复。
print
操作符允许你打印有关发布者的调试信息。- 你可以打印每个值,以及任何发生的错误。
- 使用此操作符可以了解数据流中发生了什么。
以下是一个示例,演示如何使用 print
操作符打印调试信息:
var cancellables = Set<AnyCancellable>()
let numbers = [1, 2, 3, 4, 5]
let publisher = numbers.publisher
publisher
.print("Numbers")
.sink { value in
print("Received value: \(value)")
}
.store(in: &cancellables)
在这个例子中,我们使用 print
运算符在 publisher
发出值之前打印调试信息。当我们使用 sink
方法订阅该 publisher 时,我们打印出它发出的每个值。这段代码的输出如下:
Numbers: receive subscription: ([1, 2, 3, 4, 5])
Numbers: request unlimited
Numbers: receive value: (1)
Received value: 1
Numbers: receive value: (2)
Received value: 2
Numbers: receive value: (3)
Received value: 3
Numbers: receive value: (4)
Received value: 4
Numbers: receive value: (5)
Received value: 5
Numbers: receive finished
print
运算符将调试信息添加到我们的数据流中,显示我们何时订阅、请求值和完成。通过使用 print
运算符,我们可以更好地理解我们的数据流并进行调试。
breakpoint
breakpoint
操作符允许你在 Xcode 的调试器中暂停代码执行,当一个 publisher 发送一个值时,让你可以方便地调试复杂的数据流,并理解代码的运行方式。使用这个操作符可以帮助你识别和解决 Combine 代码中的问题。
下面是一个示例,展示如何使用 breakpoint
操作符在 Xcode 的调试器中暂停代码执行:
var cancellables = Set<AnyCancellable>()
let numbers = [1, 2, 3, 4, 5]
let publisher = numbers.publisher
publisher
.breakpoint(receiveOutput: { value in
return value > 3
})
.sink { value in
print("Received value: \(value)")
}
.store(in: &cancellables)
在这个例子中,我们使用breakpoint
操作符来在Xcode调试器中暂停代码执行,当publisher
发送大于3的值时就会触发。在Xcode中运行代码时,当它遇到大于3的值时,调试器将暂停在带有breakpoint
操作符的行上,让我们可以检查数据流的状态并识别任何问题。
breakpoint
操作符是一个强大的调试工具,它允许我们暂停代码执行并实时检查数据流,帮助我们快速、高效地识别和解决问题。
breakpointOnError
Swift Combine 中的 breakpointOnError
操作符是一个调试工具,它允许您暂停发布者流并检查导致流终止的错误。当调试复杂的发布者链以帮助确定错误的位置和可能导致错误的原因时,这将是有用的。
以下是在发布者链中使用 breakpointOnError
操作符的示例:
enum ExampleError: Error {
case example
}
var cancellables = Set<AnyCancellable>()
let numbers = [1, 2, 3, 4, 5].publisher
numbers
.map { $0 * 2 }
.filter { $0 % 3 == 0 }
.tryMap { value -> Int in
if value == 6 {
throw ExampleError.example
}
return value
}
.breakpointOnError()
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("Finished")
case .failure(let error):
print("Error: \(error)")
}
},
receiveValue: { value in
print(value)
}
)
.store(in: &cancellables)
在此示例中,我们创建一个 Publisher
,从数组中发出一系列整数。然后链接多个操作符,包括 map
、filter
和 tryMap
,来转换和过滤发出的值。
我们在 tryMap
操作符之后插入 breakpointOnError
操作符。如果在发布者链执行期间发生错误,则流会暂停等待你调试错误,然后才会继续进行。
在这个示例中,当遇到值 6 时,我们故意抛出一个错误。当 tryMap
操作符遇到这个错误时,breakpointOnError
操作符将暂停流,让你检查错误和当前发布者链的状态。一旦调试完成,你可以通过调试器中的继续按钮来恢复流。
breakpointOnError
操作符在调试复杂的发布者链时特别有用,尤其是在很难追踪错误源的情况下。
handleEvents
在 Swift Combine 中,handleEvents
运算符允许你在订阅、收到值、完成等不同阶段的生命周期中将闭包附加到一个发布者上。这对于调试和监视发布者的行为以及在发布者链中执行额外的副作用非常有用。
以下是在一个发布者链中使用 handleEvents
运算符的示例:
var cancellables = Set<AnyCancellable>()
let numbers = [1, 2, 3, 4, 5].publisher
numbers
.handleEvents(
receiveSubscription: { subscription in
print("Received subscription: \(subscription)")
},
receiveOutput: { value in
print("Received value: \(value)")
},
receiveCompletion: { completion in
switch completion {
case .finished:
print("Finished")
case .failure(let error):
print("Error: \(error)")
}
},
receiveCancel: {
print("Cancelled")
},
receiveRequest: { demand in
print("Received demand: \(demand)")
}
)
.map { $0 * 2 }
.sink { print($0) }
在这个例子中,我们创建了一个Publisher
,它从一个数组中发出整数序列。然后我们使用handleEvents
操作符在不同的时刻附加闭包到该Publisher
,例如在订阅和完成事件前后。这对于调试和监视Publisher
的行为以及在Publisher
链中执行附加副作用非常有用。
我们使用handleEvents
操作符的不同闭包参数来打印关于该Publisher
的订阅、输出、完成、取消和需求事件的信息。
然后,我们链式调用map
操作符来使发出的值加倍,最后使用sink
操作符打印出最终值。
通过使用handleEvents
操作符,我们可以在Publisher
的生命周期的不同阶段监视和调试其行为。例如,我们可以打印下游订阅者发出的需求请求或检测何时取消了订阅。我们还可以在Publisher
链中执行其他附加副作用,例如日志记录或更新用户界面元素。
在下一章中,我们将探讨Swift Combine和传统编程模型之间的差异,并了解Swift Combine如何改进我们的编程流程。
文章首发发布地址:Github