问题
How can I ensure that a subscriber to an Observable
will receive the onNext
event after another subscriber?
My example is the following:
let firstNameObservable = firstName.asObservable().shareReplay(1)
let lastNameObservable = lastName.asObservable().shareReplay(1)
let bioObservable = bio.asObservable().shareReplay(1)
let websiteObservable = website.asObservable().shareReplay(1)
firstNameObservable.subscribe(onNext: { [unowned self] firstName in self.accountDetails.firstName = firstName }).disposed(by: disposeBag)
lastNameObservable.subscribe(onNext: { [unowned self] lastName in self.accountDetails.lastName = lastName }).disposed(by: disposeBag)
bioObservable.subscribe(onNext: { [unowned self] bio in self.accountDetails.bio = bio }).disposed(by: disposeBag)
websiteObservable.subscribe(onNext: { [unowned self] website in self.accountDetails.website = website }).disposed(by: disposeBag)
Observable.combineLatest(firstNameObservable, lastNameObservable, bioObservable, websiteObservable)
{ [unowned self] _, _, _, _ in return self.accountDetails.validForSignUp }
.bind(to: isValid)
.disposed(by: disposeBag)
I would like for that last combineLatest
binding to trigger after any of the 4 subscriptions
above it have already executed. This is because it is only after the properties on accountDetails
have been set that the validForSignUp
will be accurate.
I am aware that a solution to this problem would be to make validForSignUp
a Variable
and observe it, but let's assume that is not possible.
回答1:
I would do something more like this:
let accountDetails = AccountDetails(firstName: firstName.orEmpty.asObserable(), lastName: lastName.orEmpty.asObservable(), bio: bio.orEmpty.asObservable(), website: website.orEmpty.asObservable())
accountDetails.validForSignup
.bind(to: isValid)
.disposed(by: bag)
struct AccountDetails {
let validForSignup: Observable<Bool>
init(firstName: Observable<String>, lastName: Observable<String>, bio: Observable<String>, website: Observable<String>) {
let firstNameValid = firstName.map { $0.isValidFirstName }
let lastNameValid = lastName.map { $0.isValidLastName }
let bioValid = bio.map { $0.isValidBio }
let websiteValid = website.map { $0.isValidWebsite }
validForSignup = Observable.combineLatest([firstNameValid, lastNameValid, bioValid, websiteValid]) { $0.allTrue() }
}
}
extension String {
var isValidFirstName: Bool {
return !isEmpty // put approprate code here
}
var isValidLastName: Bool {
return !isEmpty // put approprate code here
}
var isValidBio: Bool {
return !isEmpty // put approprate code here
}
var isValidWebsite: Bool {
return !isEmpty // put approprate code here
}
}
extension Sequence where Iterator.Element == Bool {
func allTrue() -> Bool {
return !contains(false)
}
}
回答2:
You can handle your data validation with a single subscribe. Here's how it can be done where I'll bind the combined events to an Observable that performs validation of the combined results.
let fields = [firstNameObservable,
lastNameObservable,
bioObservable,
websiteObservable]
let validFields = Observable.combineLatest(fields).bind(to: validateFields)
The Observable that will deliver the validation of the combined results could look something like the following function.
func validateFields<T>(allFields: Observable<[T]>) -> Observable<Bool>
{
return allFields.map { fieldData in
var result: Bool = false
/* Is the data valid? */
return result;
}
}
The Observable validFields
will be updated with a validation result every time there is new data from any of the field Observables. With combineLatest
, you have the added benefit that the order of your source Observables is maintained. This solution is accomplished without resorting to storage of state making it completely reactive.
来源:https://stackoverflow.com/questions/44204583/rxswift-shared-subscription-execution-order