您应该知道的6个Swift组合运算符

本文的翻译是在预期高级课程“ iOS Developer”开始时准备的








在本文中,我们将研究六个有用的Combine运算符。我们将通过示例进行此操作,并在Xcode Playground中进行实验。



本文末尾提供了源代码。



好吧,事不宜迟,让我们开始吧。



1.前置



这组语句使我们可以将事件,值或其他发布者添加到我们的原始发布者(实际上是“添加”):



import Foundation
import Combine

var subscriptions = Set<AnyCancellable>()

func prependOutputExample() {
    let stringPublisher = ["World!"].publisher
    
    stringPublisher
        .prepend("Hello")
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}


结果:HelloWorld按顺序输出:







现在让我们添加另一个相同类型的发布者:



func prependPublisherExample() {
    let subject = PassthroughSubject<String, Never>()
    let stringPublisher = ["Break things!"].publisher
    
    stringPublisher
        .prepend(subject)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    subject.send("Run code")
    subject.send(completion: .finished)
}


结果与上一个类似(请注意,我们需要发送一个事件.finished让操作员操作.prepend):







2.附加



运算符.append(字面意思是“添加到末尾”)的工作方式类似.prepend,但是在这种情况下,我们向原始发布者添加值:



func appendOutputExample() {
    let stringPublisher = ["Hello"].publisher
    
    stringPublisher
        .append("World!")
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}


结果,我们看到HelloWorld输出到控制台:







与我们之前.prepend添加另一个Publishera相似,我们也为操作员提供了以下选项.append







3.切换到最新



更为复杂的运算符.switchToLatest使我们可以将一系列发布者合并为一个事件流:



func switchToLatestExample() {
    let stringSubject1 = PassthroughSubject<String, Never>()
    let stringSubject2 = PassthroughSubject<String, Never>()
    let stringSubject3 = PassthroughSubject<String, Never>()
    
    let subjects = PassthroughSubject<PassthroughSubject<String, Never>, Never>()
    
    subjects
        .switchToLatest()
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    subjects.send(stringSubject1)
    
    stringSubject1.send("A")
    
    subjects.send(stringSubject2)
    
    stringSubject1.send("B") // 
    
    stringSubject2.send("C")
    stringSubject2.send("D")
    
    subjects.send(stringSubject3)
    
    stringSubject2.send("E") // 
    stringSubject2.send("F") // 
    
    stringSubject3.send("G")
    
    stringSubject3.send(completion: .finished)
}


这是代码中发生的事情:



  • 我们创建了三个对象PassthroughSubject,我们将向这些对象发送值。
  • 我们创建一个PassthroughSubject分派其他对象的主对象PassthroughSubject
  • 我们运送stringSubject1到主要主题。
  • stringSubject1 获得值A。
  • 我们调度stringSubject2到主要主题,自动丢弃stringSubject1事件。
  • 同样,我们向发送值stringSubject2,连接stringSubject3到它并向其发送完成事件。


结果是输出ACDG







为简单起见,该函数isAvailable返回一个随机值Bool某些延迟之后。



func switchToLatestExample2() {
    func isAvailable(query: String) -> Future<Bool, Never> {
        return Future { promise in
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                promise(.success(Bool.random()))
            }
        }
    }
    
    let searchSubject = PassthroughSubject<String, Never>()
    
    searchSubject
        .print("subject")
        .map { isAvailable(query: $0) }
        .print("search")
        .switchToLatest()
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    searchSubject.send("Query 1")
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        searchSubject.send( "Query 2")
    }
}


感谢操作员,.switchToLatest我们实现了我们想要的。仅显示一个布尔值:







4.合并(与:)



我们通常.merge(with:)将两个Publisherss组合起来,好像我们只是从一个值中获取值一样:



func mergeWithExample() {
    let stringSubject1 = PassthroughSubject<String, Never>()
    let stringSubject2 = PassthroughSubject<String, Never>()
    
    stringSubject1
        .merge(with: stringSubject2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    stringSubject1.send("A")
    
    stringSubject2.send("B")
    
    stringSubject2.send("C")
    
    stringSubject1.send("D")
}


结果是元素的交替序列:







5.combine最新



运算符.combineLatest发布一个包含每个发布者最新值的元组。



为了说明这一点,请考虑以下真实示例:我们有一个用户名,密码UITextFields和一个继续按钮。我们希望禁用该按钮,直到用户名至少五个字符并且密码至少八个字符。我们可以使用运算符轻松实现此目的.combineLatest



func combineLatestExample() {
    let usernameTextField = CurrentValueSubject<String, Never>("")
    let passwordTextField = CurrentValueSubject<String, Never>("")
    
    let isButtonEnabled = CurrentValueSubject<Bool, Never>(false)
    
    usernameTextField
        .combineLatest(passwordTextField)
        .handleEvents(receiveOutput: { (username, password) in
            print("Username: \(username), password: \(password)")
            let isSatisfied = username.count >= 5 && password.count >= 8
            isButtonEnabled.send(isSatisfied)
        })
        .sink(receiveValue: { _ in })
        .store(in: &subscriptions)
    
    isButtonEnabled
        .sink { print("isButtonEnabled: \($0)") }
        .store(in: &subscriptions)
    
    usernameTextField.send("user")
    usernameTextField.send("user12")
    
    passwordTextField.send("12")
    passwordTextField.send("12345678")
}


单击 一次usernameTextField passwordTextField接收user1212345678因此满足条件,并且按钮被激活:







6.zip



运营商.zip从每个发布者处提供一对匹配值。假设我们要确定两个发布者是否发布了相同的值Int



func zipExample() {
    let intSubject1 = PassthroughSubject<Int, Never>()
    let intSubject2 = PassthroughSubject<Int, Never>()
    
    let foundIdenticalPairSubject = PassthroughSubject<Bool, Never>()
    
    intSubject1
        .zip(intSubject2)
        .handleEvents(receiveOutput: { (value1, value2) in
            print("value1: \(value1), value2: \(value2)")
            let isIdentical = value1 == value2
            foundIdenticalPairSubject.send(isIdentical)
        })
        .sink(receiveValue: { _ in })
        .store(in: &subscriptions)
    
    foundIdenticalPairSubject
        .sink(receiveValue: { print("is identical: \($0)") })
        .store(in: &subscriptions)
    
    intSubject1.send(0)
    intSubject1.send(1)
    
    intSubject2.send(4)
    
    intSubject1.send(6)
    intSubject2.send(1)
    intSubject2.send(7)
    
    intSubject2.send(9) //  ,       
}


我们有来自intSubject1的以下对应值intSubject2



  • 0和4
  • 1和1
  • 6和7


9由于intSubject1相应的值尚未发布, 因此不会显示后一个值







资源资源



源代码可在Gist上找到



结论



对其他类型的合并运算符感兴趣吗?随时访问我的其他文章:






All Articles