Swift generics - Cannot explicitly specialize a generic function - ios

I'm trying to call a generic function and i get this error:
Cannot explicitly specialize a generic function
My code:
public func parse<T: Codable>(request: HTTPRequestProtocol,
completion: #escaping (T?) -> Void) {
///....
}
//
parser.parse<Person>(request: request, onSuccess: { (codable) in
//Error: Cannot explicitly specialize a generic function
}
How can i fix it?
Thanks

There is this rule in Swift that you must not explicitly say (using <>s) what the generic parameters of a generic method are. You must give clues to the type inference engine to let it figure the generic parameters out. In this case, you can annotate the type of closure parameter, so that the closure has a type of (Person) -> Void. With this information, the compiler can figure the type of T out.
parser.parse(request: request, onSuccess: { (codable: Person) in ... }
In other cases, you might have to take in an extra parameter of type T.Type. For example, if your function only takes a type parameter and no value parameters:
func foo<T>() { ... }
In that case, you'd need to add an extra parameter:
func foo<T>(_ type: T.Type) { ... }
so that you can use it as:
foo(Person.self)

Related

How to create typealias for completion handlers with named arguments

What I have so far is this:
I've defined typealias completion handler
typealias UserCompletionHandler = (_ object: User?, _ error: ApiError?) -> Void
And I've created a service function that is using this typealias
func login(email: String, password: String, completion: UserCompletionHandler) {
// ...
// this part here handles API call and parsing logic
// ...
completion(user, nil)
}
What I want to achieve is to have more readable completion callback with parameters by introducing named arguments. Idea is to end up with this:
completion(object: user, error: nil)
Or even better to make error parameter optional, so I can just call
completion(object: user)
Issue is that I can't find a way to change typealias definition to achieve this.
Apparently this is not possible. You can find the explanation behind this choice in the swift evolution proposal: 0111-remove-arg-label-type-significance.md
Function types may only be defined in terms of the types of the formal parameters and the return value.
Writing out a function type containing argument labels will be prohibited
Not sure if this answer is ideal, but you could use a tuple as your input argument:
typealias UserCompletionHandler = ((object: User?, error: ApiError?)) -> Void
and the usage would look like this:
completion((object: user, error: nil))

Passing concrete instance to closure that expects a protocol parameter

In my app, I have a function to call a specific API endpoint of mine, and that function accepts a closure as a completion handler. That closure accepts a Result of my custom Decodable type (Category) in the success case, and an Error in the failure case. Altogether, its method signature looks like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], Error>) -> Void) -> Void
This function calls out to Alamofire to determine the languages that the server supports, and then get the list of all possible restaurant categories. It's implemented like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], Error>) -> Void) -> Void{
API.localizedRequest(API.categories) { (request: DataRequest) in
request.responseDecodable(of: [Category].self) { (response: DataResponse<[Category], AFError>) in
completion(response.result)
}
}
}
However, on the line with completion(response.result), I get a compiler error that says Cannot convert value of type 'Result<[Category], AFError>' to expected argument type 'Result<[Category], Error>'. This error goes away if I change the closure my method accepts to accept an AFError in the failure case, like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], AFError>) -> Void) -> Void{
API.localizedRequest(API.categories) { (request: DataRequest) in
request.responseDecodable(of: [Category].self) { (response: DataResponse<[Category], AFError>) in
completion(response.result)
}
}
}
Alamofire's AFError conforms to Error, so it seems to me that this should work just fine. I know I can parse Alamofire's Result myself and generate my own to pass to my completion handler, but I'd rather not write all that extra custom code if I don't have to. How can I get the type system to understand that this should be ok?
Simply put, in (at least the current version of) Swift, if Sub is a subtype of Base, that doesn't mean that Container<Sub> is a subtype of Container<Base>.
In fact, Container<Sub> and Container<Base> are unrelated types.
So, while we can do the following:
protocol Car {}
struct Toyota: Car {}
let a: Car = Toyota()
but we can't generally (with notable exception of Swift's standard library collection types) do this:
struct Container<T> {}
let c: Container<Car> = Container<Toyota>() // ERROR
It is said that Container<Car> and Container<Toyota> are not covariant
Result has a mapError function that should make it fairly painless:
completion(response.result.mapError { $0 as Error } )

How to return a default value for a generic parameter in a function in Swift?

I'm using generics to dynamically assign the type of the value that the response should be, at the call site of the function.
The function below is the only accessible method my FirebaseClient class has.
It is called in many different places.
To make sure the correct object is in the response, I insert what object I'm expecting at the call site of the function. Here is the catch. If I'm posting a value to database, I dont expect any response, and only care about error, but am forced to put an arbitrary type for param/argument, even though i know it will be nil for such cases.
How can I set a default value for generic parameter, so that no one could hypothetically come along and insert an object as response when they won't get one?
Hopefully the code speaks for itself and makes more sense than I did.
Other info:
Inside the method below, if its a GET request, I initialize T with the json response. As you see the generic has to conform to QueryType protocol, which just contains an initializer, so each object that is T, has an initializer to handle response in its own way.
func fireRequest<T: QueryType>(_ request: FirebaseRequest, completion: #escaping (_ data: [T]? = [], _ error: FirebaseError?) -> Void ) {
// setting default value to data param throws error.
}
Successful use case:
client.fireRequest(FirebaseRequest.observe(path: path), completion: { (data: [User], error: FirebaseError? ) in
// Since I infer 'T' to be of type User, i can expect a collection of users, or single user as my response. This is what I want.
})
Error Use case:
client.fireRequest(FirebaseRequest.setValue(path: path, value: locationData), completion: { (_, error: FirebaseError? ) in
// error: generic parameter 'T' could not be inferred.
// In this case, I am posting some data to database, so I dont expect a response, but putting underscore throws error.
})
I am not exactly sure what you are asking but you can use T() to generate the defualt value for the type.
As for the underscore it is because (_, FirebaseError?)->Void is not the same type as ([T], FirebaseError?)->Void. So your closure should be
{(_:[AnyObject]?, error: FirebaseError?) in code }
If you want this to use generics you would need to define some function like
func errorCaseCompletetion<T>( _ : [T]?, error: FirebaseError?){
//code
}
And then you just pass the function name as the completetion block.

Firebase Swift 3 Database crashes on setValue withCompletionBlock

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.
While there is an accepted answer, which provides some clarity, explaining that it's a Swift bug is not really accurate. That being said, the explanation is accurate but not for this issue.
Allowing the closure to be added to setValue in the first place is the real bug.
A more accurate answer is that there is no completion block/closure for the setValue function, which is why it fails.
The specific function -setValue: does NOT have a closure, which is why it's crashing. i.e. it's an incorrect implementation in your code. Per the docs:
func setValue(_ value: Any?)
note that the setValue function does NOT have a closure and if you add one the function will have no idea what to do with that data.
To use a completion block/closure, you must call the correct function which is
-setValue:withCompletionBlock:
Bottom line is you can't randomly add a parameter or call to a function that's not designed to accept it.
This is obviously not valid but conceptually it's the same error.
let a = "myString"
a.append("x") { (error: Error?) }
In this case the compiler knows the the string.append function doesn't have a closure option and catches it before compiling.
So go a tad further, this code complies & runs but also generates an error
ref.child("child").setValue("value") { }
Again, setValue doesn't have a closure so this code is improperly implemented.
To clarify, given a class
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
}
}
Note that setValue:aString is a totally different function than setValue:aString:someCompletionHandler
The only parameter that can be based to setValue:aString is a String as the first and only parameter.
The setValue:aString:someCompletionHandler will be passed two parameters, a String in the first position and a completionHandler in the second position.
The actual completion block is the second parameter passed.
param1, param2
------, ---------------
string, completionBlock
This is why
setValue(value) {}
is improperly formatted whereas
setValue(value, withBlock: {})
is properly formatted.

Generic parameter constrained by other generic parameter

Due to Swift's lack of covariance, I needed some workaround. I'm coming from Java world, so I instinctively tried to create constraint from one type to other generic type.
So I wrote the following class:
class Factory<T: AnyObject> {
let factoryClosure: () -> T
init(closure: () -> T) {
factoryClosure = closure
}
init<CHILD: T>(childFactory: Factory<CHILD>) {
factoryClosure = { () -> T in
return otherFactory.create()
}
}
func create() -> T {
return factoryClosure()
}
}
I expected this to work just fine. I have the T defined and CHILD should be a subclass of T. Swift compiler however disagrees and shows the following error on the line with init<CHILD: T>.
Inheritance from non-protocol, non-class type 'T'
I tried the generic parameter inheritance in different scenario as well. Adding the following method into the class (and removing the init that was causing the compile error).
func to<OTHER where OTHER: AnyObject, T: OTHER>() {
}
This yields basically the same output.
Type 'T' constrained to non-protocol type 'OTHER'
Anything I though may work did not and ended with similar error message. Is this a bug in Swift? Or am I missing something? Or is it a feature of Swift and will never work as I thought?
If you want to pass any Factory<T> where T is of type AnyObject you just have to write:
init(childFactory: Factory<T>) {
factoryClosure = { () -> T in
return otherFactory.create()
}
}
because T is automatically constrained by your class.

Resources