我有一个名为proceed的登录按钮,并且有两个@Published
属性,表示UITextField
中当前键入的文本。我想要的是,将.filter { !$0.0.isEmpty && !$0.1.isEmpty}
的结果分配给我的UIButton
的Bool
变量isEnabled
目前我的结局是:
passwordTxtf.textView.$text
.combineLatest(loginTxtf.textView.$text)
.filter { !$0.0.isEmpty && !$0.1.isEmpty}
.sink(receiveValue: { [weak self] in
guard let weakSelf = self else { return }
weakSelf.loginEntered = $0
weakSelf.passEntered = $1
weakSelf.setProceedEnableState()
})
.store(in: &subscriptions)
其中setProceedEnableState()
是一个检查类变量并因此确定按钮启用状态的函数。
但我想在";管道";,或者以更优雅的方式
您可以构建代码,使textview的每个信号都分配给Subject
s。下面是一个基于您的用例的小示例
let isProcessEnabled = PassthroughSubject<Bool, Never>()
let loginEntered = PassthroughSubject<Bool, Never>()
let passEntered = CurrentValueSubject<Bool, Never>(false)
passwordTxtf.textView.$text.map { !$0.isEmpty }
.subscribe(passEntered)
.store(in: &store)
loginTxtf.textView.$text.map { !$0.isEmpty }
.subscribe(loginEntered)
.store(in: &store)
Publishers.CombineLatest(passEntered, loginEntered)
.map { $0 && $1 }
.subscribe(isProcessEnabled)
.store(in: &store)
isProcessEnabled.sink { isEnabled in
print("Is processing enabled", isEnabled)
}.store(in: &store)
这样做的最大优点是可以将isProcessEnabled
、loginEntered
和passEntered
移动到视图模型或其他对象中,这将使整个代码更易于测试。此外,你可以看到,你可以以一些有趣的方式将这些主题中的每一个结合起来,创建一些新的发布者/主题。
class ViewModel {
private var store = Set<AnyCancellable>()
var isProcessingEnabled: AnyPublisher<Bool, Never> {
return processEnabled.eraseToAnyPublisher()
}
var isLoginEntered: AnyPublisher<Bool, Never> {
return loginEntered.eraseToAnyPublisher()
}
var isPassEntered: AnyPublisher<Bool, Never> {
return passwordEntered.eraseToAnyPublisher()
}
func subscribeLoginText(publisher: AnyPublisher<String, Never>) {
publisher.map { !$0.isEmpty }.subscribe(loginEntered).store(in: &store)
}
func subscribePasswordText(publisher: AnyPublisher<String, Never>) {
publisher.map { !$0.isEmpty }.subscribe(passwordEntered).store(in: &store)
}
private var processEnabled = CurrentValueSubject<Bool, Never>(false)
private var loginEntered = PassthroughSubject<Bool, Never>()
private var passwordEntered = PassthroughSubject<Bool, Never>()
init() {
Publishers.CombineLatest(passwordEntered, loginEntered)
.map { $0 && $1 }
.subscribe(processEnabled)
.store(in: &store)
}
}
而且,你可以看到,现在你已经模块化了应用程序,你的视图模型不需要知道你的视图/视图控制器的细节。最重要的是,您的代码现在非常可测试。您可以创建一个视图模型的实例并断言您的假设。
以下是上面视图模型实现的一个小样本测试用例
class ViewModelTest: XCTestCase {
var store: Set<AnyCancellable> = []
var login = PassthroughSubject<String, Never>()
var password = PassthroughSubject<String, Never>()
var isProcessingEnabled = CurrentValueSubject<Bool, Never>(false)
var viewModel: ViewModel = ViewModel()
override func setUp() {
store = []
viewModel = ViewModel()
login = PassthroughSubject<String, Never>()
password = PassthroughSubject<String, Never>()
isProcessingEnabled = CurrentValueSubject<Bool, Never>(false)
viewModel.isProcessingEnabled.subscribe(isProcessingEnabled).store(in: &store)
viewModel.subscribeLoginText(publisher: login.eraseToAnyPublisher())
viewModel.subscribePasswordText(publisher: password.eraseToAnyPublisher())
}
func testThatProcessingIsDisabledInitially() {
XCTAssertFalse(isProcessingEnabled.value, "Processing is enabled")
}
func testThatProcessingIsDisabledWhenOnlyLoginIsEmpty() {
login.send("")
password.send("b")
XCTAssertFalse(isProcessingEnabled.value, "Processing is enabled")
}
func testThatProcessingIsDisabledWhenOnlyPasswordIsEmpty() {
login.send("a")
password.send("")
XCTAssertFalse(isProcessingEnabled.value, "Processing is enabled")
}
func testThatProcessingIsEnabledWhenLoginAndPasswordAreNotEmpty() {
login.send("a")
password.send("b")
XCTAssertTrue(isProcessingEnabled.value, "Processing is disabled")
}
}