欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SwiftUI Combine的CurrentValueSubject

程序员文章站 2024-03-24 11:55:22
...

先说结论,CurrentValueSubject适合那些有初始状态的数据。

CurrentValueSubject介绍

和PassthroughSubject相比,CurrentValueSubject在声明时候需要有一个初始值

在playground中输入以下代码

let curPublisher = CurrentValueSubject<String, Never>("No.1")

我们声明了一个发布字符串的publisher,初始值发布“No.1”。

接着我们来订阅这个publisher,接收他的发布值

let cancellable: AnyCancellable = curPublisher
    .print("_current_")
    .sink { completion in
        switch completion {
        case .finished:
            print("finished")
        case .failure(let error):
            print("Got a error: \(error)")
        }
    } receiveValue: { str in
        print(str)
    }

运行代码,打印信息如下

_current_: receive subscription: (CurrentValueSubject)
_current_: request unlimited
_current_: receive value: (No.1)
No.1

这表明我们已经接收到了发布值,发布值是CurrentValueSubject声明时的初始值。

和PassthroughSubject不同,CurrentValueSubject在被订阅时,会发布他的初始值。

我们把代码改为如下:

let curPublisher = CurrentValueSubject<String, Never>("No.1")

curPublisher.send("No.2")

let cancellable: AnyCancellable = curPublisher
    .print("_current_")
    .sink { completion in
        switch completion {
        case .finished:
            print("finished")
        case .failure(let error):
            print("Got a error: \(error)")
        }
    } receiveValue: { str in
        print(str)
    }

运行,打印信息如下

_current_: receive subscription: (CurrentValueSubject)
_current_: request unlimited
_current_: receive value: (No.2)
No.2

可以看到,我们依旧接收到了值,只不过是最后一次publisher发布的值“No.2”,也就是说,CurrentValueSubject生成的publisher在被订阅后,会接到他之前发布过的最后一个值,如果没有发布过,则接收到他的初始发布值。

同时,CurrentValueSubject还可以用 .value 的方式来改变他发布的值,如下

curPublisher.value = "No.5"

他等同于

curPublisher.send("No.5")

我们把代码再次修改如下:

let curPublisher = CurrentValueSubject<String, Never>("No.1")

// 订阅前发布No.2
curPublisher.send("No.2")

let cancellable: AnyCancellable = curPublisher
    .print("_current_")
    .sink { completion in
        switch completion {
        case .finished:
            print("finished")
        case .failure(let error):
            print("Got a error: \(error)")
        }
    } receiveValue: { str in
        print(str)
    }

// 订阅后发布 No.4
curPublisher.send("No.4")

// 订阅后发布 No.5 .value模式
curPublisher.value = "No.5"

// 订阅结束
curPublisher.send(completion: .finished)

// 订阅结束后发布 No.6
curPublisher.value = "No.6"

此时打印信息如下:

_current_: receive subscription: (CurrentValueSubject)

_current_: request unlimited

_current_: receive value: (No.2)

No.2

_current_: receive value: (No.4)

No.4

_current_: receive value: (No.5)

No.5

_current_: receive finished

finished

可以看出,订阅后的过程和PassthroughSubject是一样的,在订阅结束后,我们无法再收到任何发布值。Failure的情况也是一致,我们就不再举例了。

实际应用

实际的工程中,我们有使用CurrentValueSubject的情况,比如如下Demo

SwiftUI Combine的CurrentValueSubject

我们假设这是一个显示汽车油耗的Demo,在行驶10km时,邮箱还应有90%油量,50km时,有50%油量,100km时,有20%油量。初始油量是100%

我们来使用CurrentValueSubject实现这个功能

import SwiftUI
import Combine

struct CurrentValueSubjectView: View {
    
    @StateObject var vm = ViewModel()
    @State var text = ""
    
    var body: some View {
        HStack(spacing: 40.0) {
           
            HStack(alignment: .firstTextBaseline, spacing: 10.0) {
                Text("\(vm.oil)")
                    .font(.system(size: 90))
                    .bold()
                Text("%")
                    .font(.largeTitle)
                    .bold()
            }
            
            VStack(alignment: .leading, spacing: 30.0) {
                Button(action: {
                    vm.oilPublisher.send(90)
                }, label: {
                    Text("10km")
                })
                Button(action: {
                    vm.oilPublisher.value = 50
                }, label: {
                    Text("50km")
                })
                Button(action: {
                    vm.oilPublisher.send(20)
                }, label: {
                    Text("100km")
                })
            }
            
        }
    }
}

extension CurrentValueSubjectView {
    class ViewModel: ObservableObject {
        
        @Published var oil: Int = 0
        var oilPublisher = CurrentValueSubject<Int, Never>(100)
        
        var cancellable: AnyCancellable?
        
        init() {
            cancellable = oilPublisher
                .print("_current_")
                .sink { [weak self] value in
                    self?.oil = value
                }
        }
    }
}

 我们在ViewModel中声明了一个CurrentValueSubject的publisher,它发布一个Int型数据,初始值为100。

在ViewModel的初始化Init函数中,我们订阅这个publisher,根据之前的介绍,它会接收到publisher发布的初始值,100,我们把它赋值给 self.oil,oil是一个@Published变量,在View中,我们用这个变量来显示油量剩余百分比

Text("\(vm.oil)")
    .font(.system(size: 90))
    .bold()

所以,即时我们在ViewModel中给了@Published var oil = 0 一个初始值0,但是因为CurrentValueSubject的原因,他会接收到第一次初始值的发布,所以,oil被赋值为100,demo运行时,初始显示的是100%油量。

点击10km、50km、100km3个按钮时,会分别利用CurrentValueSubject生成的publisher发布新的值,这些值都会被接收并赋值给oil变量,然后完成View的刷新显示。

相关标签: Combine swiftui