在这个章节里,咱们将学习怎么运用不同类型的操作符来处理从发布者发送出来的数据,它们是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的呼应式编程中创立更杂乱的处理管道。

操作符链接

能够将操作符链接在一同,以创立更杂乱的数据转化,每个步骤的转化都会创立一个新的发布者。现在,让咱们进行一些操作符链接。

  • 链接mapfilter
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),它会宣布整数值,然后链接mapfilter操作符。map操作符会将宣布的值加倍,filter操作符仅传递可被3整除的值。然后订阅者接纳到过滤和映射的值(6),并将它们打印到操控台。

  • 链接flatMapfilter
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),它会宣布字符串值,然后链接flatMapfilter操作符。flatMap操作符将每个字符串值转化为一个宣布该字符串计数的新发布者。filter操作符仅传递计数为偶数的值。然后订阅者接纳到过滤和映射的值(6),并将它们打印到操控台。

  • 链接mapfilterreduce
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发布者,usernamepassword,用于接纳用户输入的值。咱们运用Zip操作符将这两个发布者组合成一个新的发布者credentials,它宣布包含每个输入发布者的最新值的元组。然后咱们运用map操作符将元组转化为更有意义的类型,以习惯咱们的用例。

终究,咱们运用sink操作符订阅credentials发布者并打印它宣布的最新值。然后,咱们向usernamepassword发布者别离发送两对值,并调查sink操作符的输出。

这个比如的输出是两个元组,别离是发送到usernamepassword发布者的用户名和暗码值。然后,username发送了完结事情,导致zipped发布者也完结。

类似地,您能够运用Publishers.Zip3Publishers.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 发布者 publisher1publisher2,它们都宣布整数值。咱们运用 merge 办法将这两个发布者兼并成一个名为 mergedPublisher 的新发布者,它会按接纳到的次序从两个发布者中宣布值。然后咱们运用 sink 运算符订阅 mergedPublisher 并打印它宣布的最新值。

终究,咱们向 publisher1publisher2 别离发送四个整数值,并调查 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 发布者,别离是 numbers1numbers2,它们都会宣布整数值。咱们运用 combineLatest 操作符将两个发布者的最新值组合起来,并将组合后的成果作为元组向下流发布。然后咱们运用 sink 操作符订阅 combinedPublisher 并打印它宣布的最新组合值。

终究,咱们向 numbers1numbers2 别离发送了四个整数值,并调查 sink 操作符的输出。正如预期的那样,输出包含 numbers1numbers2 的最新组合值,每逢其中任何一个发布者宣布新值时都会更新。

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 操作符答应你打印有关发布者的调试信息。
  • 你能够打印每个值,以及任何产生的过错。
  • 运用此操作符能够了解数据流中产生了什么。

以下是一个示例,演示怎么运用 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,从数组中宣布一系列整数。然后链接多个操作符,包含 mapfiltertryMap,来转化和过滤宣布的值。

咱们在 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

Photo by KOMMERS on Unsplash