订阅者在 Firebase returns 之前返回值 SwiftUI 中的值
Subscriber returning value before Firebase returns a value in SwiftUI
我订阅了@Published 文本字段。我通过 class.
的 init() 函数添加订阅者
此订阅者的地图会扫描 Firebase 中的所有用户并检查“电子邮件”字段。如果用户输入的电子邮件在这些字段之一中,那么我将 valid 设置为 false(因为该电子邮件已被使用)。
然而,当我运行这段代码时,第二个打印行首先执行,使用默认值。然后,一段时间后, print("This email is valid") 行是 运行.
我认为这与 Firebase 调用是异步任务这一事实有关。
如何解决此问题,以便 return 值 return 成为 Firebase 调用的结果?我正在使用 Xcode 版本 12.5.1.
此外,我不确定这是否是检查电子邮件是否已被使用的最佳方法,但这是我能找到的唯一解决方案:)
代码:
func addEmailSubscriber() {
$email
.debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
.map { email -> Bool in
var valid: Bool = false
Firestore.firestore()
.collection("users")
.whereField("email", isEqualTo: email)
.getDocuments { (snapshot, err) in
guard let snapshot = snapshot else { print("Error getting snapshot"); return }
if err != nil {
print("Error occured")
return
}
if snapshot.documents.count == 0 {
print("This email is valid") // This is successfully printed after some time
valid = true
return
}
}
print(valid) // This gets printed first, with the value of "false"
return valid
}
.sink { [weak self] isValid in
self?.emailIsValid = isValid
}
.store(in: &cancellabes)
}
日志:
Combine 是处理这类事情的好工具。在这种情况下,您希望将 $email
Publisher 转换为新的 Publisher,该 Publisher returns 来自异步 Firebase 调用的值。您可以使用 map
、switchToLatest
和 Future
来执行此操作。
这是一个展示概念的基本示例(未使用您的代码):
class ViewModel : ObservableObject {
@Published var email = ""
private var cancellable : AnyCancellable?
init() {
cancellable = $email
.debounce(for: .seconds(1), scheduler: RunLoop.main)
.map { val in
Future<Bool,Never> { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
promise(.success(true))
}
}
}
.switchToLatest()
.sink(receiveValue: { val in
print("Valid?",val)
})
}
}
struct ContentView : View {
@StateObject private var viewModel = ViewModel()
var body: some View {
TextField("", text: $viewModel.email)
}
}
翻译成你的代码,它看起来像(见内联评论):
func addEmailSubscriber() {
$email
.debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
.map { email -> Bool in
Future<Bool,Never> { promise in
Firestore.firestore()
.collection("users")
.whereField("email", isEqualTo: email)
.getDocuments { (snapshot, err) in
guard let snapshot = snapshot else {
print("Error getting snapshot")
promise(.success(false)) //you may want to consider using `.failure` here, but keep in mind that that'll cancel the Publisher chain unless you erase the errors later on
return
}
if err != nil {
print("Error occured")
promise(.success(false)) //see above comment
return
}
if snapshot.documents.count == 0 {
print("This email is valid") // This is successfully printed after some time
promise(.success(true))
}
}
}
}
.switchToLatest()
.sink { [weak self] isValid in
self?.emailIsValid = isValid
}
.store(in: &cancellabes) //I kept the spelling the same, but this looks like a typo
}
我订阅了@Published 文本字段。我通过 class.
的 init() 函数添加订阅者此订阅者的地图会扫描 Firebase 中的所有用户并检查“电子邮件”字段。如果用户输入的电子邮件在这些字段之一中,那么我将 valid 设置为 false(因为该电子邮件已被使用)。
然而,当我运行这段代码时,第二个打印行首先执行,使用默认值。然后,一段时间后, print("This email is valid") 行是 运行.
我认为这与 Firebase 调用是异步任务这一事实有关。
如何解决此问题,以便 return 值 return 成为 Firebase 调用的结果?我正在使用 Xcode 版本 12.5.1.
此外,我不确定这是否是检查电子邮件是否已被使用的最佳方法,但这是我能找到的唯一解决方案:)
代码:
func addEmailSubscriber() {
$email
.debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
.map { email -> Bool in
var valid: Bool = false
Firestore.firestore()
.collection("users")
.whereField("email", isEqualTo: email)
.getDocuments { (snapshot, err) in
guard let snapshot = snapshot else { print("Error getting snapshot"); return }
if err != nil {
print("Error occured")
return
}
if snapshot.documents.count == 0 {
print("This email is valid") // This is successfully printed after some time
valid = true
return
}
}
print(valid) // This gets printed first, with the value of "false"
return valid
}
.sink { [weak self] isValid in
self?.emailIsValid = isValid
}
.store(in: &cancellabes)
}
日志:
Combine 是处理这类事情的好工具。在这种情况下,您希望将 $email
Publisher 转换为新的 Publisher,该 Publisher returns 来自异步 Firebase 调用的值。您可以使用 map
、switchToLatest
和 Future
来执行此操作。
这是一个展示概念的基本示例(未使用您的代码):
class ViewModel : ObservableObject {
@Published var email = ""
private var cancellable : AnyCancellable?
init() {
cancellable = $email
.debounce(for: .seconds(1), scheduler: RunLoop.main)
.map { val in
Future<Bool,Never> { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
promise(.success(true))
}
}
}
.switchToLatest()
.sink(receiveValue: { val in
print("Valid?",val)
})
}
}
struct ContentView : View {
@StateObject private var viewModel = ViewModel()
var body: some View {
TextField("", text: $viewModel.email)
}
}
翻译成你的代码,它看起来像(见内联评论):
func addEmailSubscriber() {
$email
.debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
.map { email -> Bool in
Future<Bool,Never> { promise in
Firestore.firestore()
.collection("users")
.whereField("email", isEqualTo: email)
.getDocuments { (snapshot, err) in
guard let snapshot = snapshot else {
print("Error getting snapshot")
promise(.success(false)) //you may want to consider using `.failure` here, but keep in mind that that'll cancel the Publisher chain unless you erase the errors later on
return
}
if err != nil {
print("Error occured")
promise(.success(false)) //see above comment
return
}
if snapshot.documents.count == 0 {
print("This email is valid") // This is successfully printed after some time
promise(.success(true))
}
}
}
}
.switchToLatest()
.sink { [weak self] isValid in
self?.emailIsValid = isValid
}
.store(in: &cancellabes) //I kept the spelling the same, but this looks like a typo
}