【问题标题】:Firebase Swift 3 Database crashes on setValue withCompletionBlockFirebase Swift 3 数据库在 setValue withCompletionBlock 上崩溃
【发布时间】:2017-05-15 08:45:00
【问题描述】:

我在带有 Swift 3 的 iOS 上使用 Firebase。

当我使用时

FIRDatabase.database().reference().child("child").setValue("value") { 
  (error: Error?, databaseReference: FIRDatabaseReference) in
    print("Error while setting value \(error)")
}   

应用程序在运行时崩溃并显示以下日志:

*** 由于未捕获的异常“InvalidFirebaseData”而终止应用程序,原因:“(nodeFrom:priority:) 无法存储 _SwiftValue 类型的对象 在 。只能存储 NSNumber、NSString、NSDictionary、 和 NSArray。'

我尝试使用相同的函数,但没有尾随闭包,由于某种原因,它可以工作!

FIRDatabase.database().reference().child("child").setValue("value", 
  withCompletionBlock: { 
    (error: Error?, databaseReference: FIRDatabaseReference) in
      print("Error while setting value \(error)")
})

尾随闭包和 Swift 3 有什么特别之处吗?

【问题讨论】:

  • 崩溃是因为您只能设置 NSNumber、NSString、NSDictionary 和 NSArray 类型的值/存储对象,而您存储的值不是任何此类类型。
  • 不确定这是否是问题所在。我尝试使用 NSString 变量并将其传递给 setValue ,但它仍然会崩溃。但无论哪种情况,不使用常规 Swift String 的闭包似乎都有效。
  • @EmadToukan 您是如何在运行时获得崩溃日志的?问这个问题可能很愚蠢!
  • @AnuragSharma 在设备上运行 firebase 应用程序,您应该能够在 Xcode 控制台中看到它。
  • @EmadToukan 显然我知道这一点,但是在通过 testflight 发送构建后我遇到了崩溃。在我调试时甚至没有出现崩溃。现在解决了!我的 NSTimer 实现导致了崩溃,我从 Crashlytics 获得了这份报告。

标签: ios swift firebase swift3 firebase-realtime-database


【解决方案1】:

tl;dr:Firebase 提供了一个 setValue(_ value: Any?, andPriority priority: Any?),当使用带有 setValue(_ value: Any?, withCompletionBlock: (Error?, FIRDatabaseReference) -> Void) 的尾随闭包时,该 setValue(_ value: Any?, andPriority priority: Any?) 匹配不正确。

解决方案:当使用多种类型的 API 时,避免使用尾随闭包。在这种情况下,首选setValue(myValue, withCompletionBlock: { (error, dbref) in /* ... */ });请不要使用setValue(myValue) { (error, dbref) in /* ... */ }

说明

这似乎是一个 Swift 错误。与其他语言(如 Java)一样,Swift 通常会选择最具体的重载。例如,

class Alpha {}
class Beta : Alpha {}

class Charlie {
    func charlie(a: Alpha) {
        print("\(#function)Alpha")
    }
    func charlie(a: Beta) {
        print("\(#function)Beta")
    }
}

Charlie().charlie(a: Alpha()) // outputs: charlie(a:)Alpha
Charlie().charlie(a: Beta() as Alpha) // outputs: charlie(a:)Alpha
Charlie().charlie(a: Beta()) // outputs: charlie(a:)Beta

但是,当重载函数匹配尾随闭包时,Swift(至少,有时)会选择更通用的类型。例如,

class Foo {
    func foo(completion: () -> Void) {
        print(#function)
    }
    func foo(any: Any?) {
        print(#function)
    }
}

func bar() {}
Foo().foo(completion: bar) // outputs: foo(completion:)
Foo().foo(any: bar) // outputs: foo(any:)
Foo().foo() { () in } // outputs: foo(any:)
// ^---- Here lies the problem
// Foo().foo(bar) will not compile; can't choose between overrides.

Any? 是比() -> Void 更通用的类型——即,“任何东西,甚至是 null”比“接收 0 个参数并返回 Void 类型的东西的函数”更广泛。但是,尾随闭包匹配Any?;这与您对匹配最特定类型的语言所期望的相反。

【讨论】:

    【解决方案2】:

    虽然有一个公认的答案,它提供了一些清晰度,但解释它是一个 Swift 错误并不真正准确。话虽如此,解释是准确的,但不适用于这个问题。

    首先允许将闭包添加到 setValue 是真正的错误。

    更准确的答案是 setValue 函数没有完成块/闭包,这就是它失败的原因。

    特定函数 -setValue: 没有闭包,这就是它崩溃的原因。即它在您的代码中的实现不正确。根据文档:

    func setValue(_ value: Any?)
    

    请注意,setValue 函数没有闭包,如果您添加一个闭包,该函数将不知道如何处理该数据。

    要使用完成块/闭包,您必须调用正确的函数

    -setValue:withCompletionBlock:
    

    底线是您不能随机添加参数或调用并非旨在接受它的函数。

    这显然无效有效,但从概念上讲它是相同的错误。

        let a = "myString"
        a.append("x") { (error: Error?) }
    

    在这种情况下,编译器知道 string.append 函数没有闭包选项,并在编译之前捕获它。

    所以再进一步,这段代码符合并运行,但也会产生错误

    ref.child("child").setValue("value") { }
    

    同样,setValue 没有闭包,所以这段代码没有正确实现。

    为了澄清,给定一个类

    class MyClass {
        var s = ""
        var compHandler = {}
    
        func setValue(aString: String) {
            self.s = aString
        }
    
        func setValue(aString: String, someCompletionHandler: completionHandler) {
            self.s = aString
            self.compHandler = someCompletionHandler
        }
    }
    

    请注意,setValue:aString 是一个与 setValue:aString:someCompletionHandler 完全不同的函数

    唯一可以基于setValue:aString的参数是一个String作为第一个也是唯一的参数。

    setValue:aString:someCompletionHandler 将传递两个参数,第一个位置的 String 和第二个位置的 completionHandler。

    实际完成块是传递的第二个参数。

    参数1,参数2 ------, --------------- 字符串,完成块

    这就是为什么

    setValue(value) {}
    

    格式不正确,而

    setValue(value, withBlock: {})
    

    格式正确。

    【讨论】:

    • 感谢 Jay 提供答案。即使您的答案是正确的,当最后一个参数不是函数时,函数不采用尾随闭包,但在这种情况下,有一个函数将闭包作为其最后一个参数:func setValue(_ value: Any?)func setValue(_ value: Any?, withCompletionBlock block: @escaping (Error?, FIRDatabaseReference) -> Void) -- > 这是我们上面讨论的那个。尾随闭包的全部目的是匹配作为参数传递函数的重载(setValue(value: withCompletionBlock:))
    • @EmadToukan 我明白你在说什么。问题是 setValue 没有闭包。 setValue:withCompletion 确实如此。闭包中的数据由 :withCompletion 部分传递/处理,而不是 setValue 部分。
    • 完全正确 :-) 这就是为什么我觉得这是 Swift 中的错误,而不是我的代码。
    • @EmadToukan 我同意和不同意。当 setValue 不接受闭包作为参数时,尝试将闭包传递给 setValue 是一个编码问题。然而——这是重要的部分——Xcode 不应该让你将一个闭包传递给一个一开始就不接受它的函数——就像在 a.append("x") {closure} 示例中一样。 Xcode 在编译之前停止它,所以它也应该用 .setValue 停止它。对我来说,这不是 IMO 的 Swift 错误,而是更多的编译器问题,或者可能在 Firebase API 中。你有一个很好的问题,无论哪种方式都应该解决。
    • setValue 接受闭包。这是 Firebase 文档 func setValue(_ value: Any?, withCompletionBlock block: @escaping (Error?, FIRDatabaseReference) -> Void) 中的函数定义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多