I am using Firebase on iOS with Swift 3.
When I use
FIRDatabase.database().reference().child("child").setValue("value") {
(error: Error?, databaseReference: FIRDatabaseReference) in
print("Error while setting value \(error)")
}
The app crashes on runtime with the following log:
*** Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(nodeFrom:priority:) Cannot store object of type _SwiftValue at . Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
I tried to use the same function but without the trailing closure and for some reason, it works!
FIRDatabase.database().reference().child("child").setValue("value",
withCompletionBlock: {
(error: Error?, databaseReference: FIRDatabaseReference) in
print("Error while setting value \(error)")
})
Is there something special about trailing closures and Swift 3?
tl;dr: Firebase provides a setValue(_ value: Any?, andPriority priority: Any?)
which is incorrectly matched when using a trailing closure with setValue(_ value: Any?, withCompletionBlock: (Error?, FIRDatabaseReference) -> Void)
.
Solution: When using an API that has many varieties, avoid using trailing closures. In this case, prefer setValue(myValue, withCompletionBlock: { (error, dbref) in /* ... */ })
; do not use setValue(myValue) { (error, dbref) in /* ... */ }
.
Explanation
This appears to be a Swift bug. As in other languages, such as Java, Swift generally chooses the most specific overload. E.g.,
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
However, when overloaded functions match a trailing closure, Swift (at least, sometimes) selects the more general type. E.g.,
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?
is a more general type than () -> Void
-- i.e., "anything, even null" is more broad than "a function receiving 0 parameters and returning something of type Void
". However, the trailing closure matches Any?
; this is the opposite of what you would expect from a language that matches the most specific type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With