Swift 4: cast issue with Generics - swift

I'm new to Swift, after many years as an Objective-C developer. I'm struggling to understand how type casting works with generics.
I have two functions that are doing object mapping with Alamofire + Codable and Alamofire + ObjectMapper. Something like this:
public func performRestOperationWithDecodable<ResponseClass: Decodable>(
_ restOperationType: NetworkServiceRestOperationType,
pathUrl: URLConvertible,
parameters: Parameters,
encoding: ParameterEncoding,
savedAuthType: NetworkServiceAuthType,
activityIndicator: ActivityIndicatorProtocol?,
successBlock: #escaping (_ responseObject: DataResponse<ResponseClass>) -> Void,
errorBlock:#escaping (_ error: Error, _ validResponse: Bool) -> Void) {
let restConfiguration = self.setupRestOperation(
savedAuthType: savedAuthType,
pathUrl: pathUrl,
activityIndicator: activityIndicator)
Alamofire
.request(
restConfiguration.url,
method: .get,
parameters: parameters,
encoding: encoding,
headers: restConfiguration.headers)
.validate(
contentType: Constants.Network.DefaultValidContentTypeArray)
.responseDecodableObject(
queue: self.dispatchQueue,
keyPath: nil,
decoder: JSONDecoder(),
completionHandler: { (responseObject: DataResponse<ResponseClass>) in
self.completionRestOperation(
responseObject: responseObject,
activityIndicator: activityIndicator,
successBlock: successBlock,
errorBlock: errorBlock)
})
}
public func performRestOperationWithObjectMapper<ResponseClass: BaseMappable>(
_ restOperationType: NetworkServiceRestOperationType,
pathUrl: URLConvertible,
parameters: Parameters,
encoding: ParameterEncoding,
savedAuthType: NetworkServiceAuthType,
activityIndicator: ActivityIndicatorProtocol?,
successBlock: #escaping (_ responseObject: DataResponse<ResponseClass>) -> Void,
errorBlock: #escaping (_ error: Error, _ validResponse: Bool) -> Void) {
let restConfiguration = self.setupRestOperation(
savedAuthType: savedAuthType,
pathUrl: pathUrl,
activityIndicator: activityIndicator)
Alamofire
.request(
restConfiguration.url,
method: .get,
parameters: parameters,
encoding: encoding,
headers: restConfiguration.headers)
.validate(
contentType: Constants.Network.DefaultValidContentTypeArray)
.responseObject(
queue: self.dispatchQueue,
keyPath: nil,
mapToObject: nil,
context: nil,
completionHandler: { (responseObject: DataResponse<ResponseClass>) in
self.completionRestOperation(
responseObject: responseObject,
activityIndicator: activityIndicator,
successBlock: successBlock,
errorBlock: errorBlock)
})
}
Each function has a generic type that conform to appropriate protocol needed by the mapper used (Decodable for Codable, Mappable for ObjectMapper). And these funcion compliles and worked as expected.
Now I'm trying to write a third function that have a third Generics type, but without conforming to any protocol, and forcing cast to the appropriate generics depending on a configuration parameter. Something like that:
public func performRestOperation<ResponseMappedClass :AnyObject>(
_ restOperationType: NetworkServiceRestOperationType,
pathUrl: URLConvertible,
parameters: Parameters = [:],
encoding: ParameterEncoding = URLEncoding.queryString,
savedAuthType: NetworkServiceAuthType,
activityIndicator: ActivityIndicatorProtocol?,
successBlock: #escaping (_ responseObject: ResponseMappedClass) -> Void,
errorBlock: #escaping (_ error: Error, _ validResponse: Bool) -> Void) {
if self.canDoRestOperation(errorBlock: errorBlock) != true {
return
}
switch self.mappingType {
case .Codable:
self.performRestOperationWithDecodable(
restOperationType,
pathUrl: pathUrl,
parameters: parameters,
encoding: encoding,
savedAuthType: savedAuthType,
activityIndicator: activityIndicator,
successBlock: { (responseObject: DataResponse<ResponseMappedClass>) in
let response: ResponseMappedClass = responseObject.result.value!
successBlock(response)
} as! (DataResponse<ResponseMappedClass & Decodable>) -> Void,
errorBlock: { (error: Error, validResponse: Bool) in
errorBlock(error, validResponse)
})
case .ObjectMapper:
// TODO
break
}
}
With the instruction: as! (DataResponse<ResponseMappedClass & Decodable>) -> Void, I'm trying to cast from the "generic" class type ResponseMappedClass to the same class, but with Codable support.
But that instruction doesn't compile:
Non-protocol, non-class type 'ResponseMappedClass' cannot be used
within a protocol-constrained type
After all that process, the various generics will represent the same class, for example SomeModelObject, that implements Codable, Mappable or maybe something else in future, so the usual type substitution at compile time, have to work anyway.
Any suggestions? Is it totally impossible to do in Swift?

I don't think it is possible to use a generic types in combination with protocols. The compiler doesn't consider them as class or protocol type. One more thing is wrong with your code, you are using self.mappingType to decide is it a Decodable or Mapable type. What if self.mappingType == .Codable, but ResponseMappedClass doesn't confirm to Decodable protocol? What I can suggest to you is function overloading. Create 3 functions:
public func performRestOperation<ResponseMappedClass: AnyObject>(...) where ResponseMappedClass: Decodable {
}
public func performRestOperation<ResponseMappedClass: AnyObject>(...) where ResponseMappedClass: BaseMappable {
}
public func performRestOperation<ResponseMappedClass: AnyObject>(...) {
}
in the last one just throw an error. The right funcion will be called depending on the generic parameter type.

(DataResponse<ResponseMappedClass & Decodable>)
What I think you are doing wrong is that ResponseMappedClassis a class and Decodable on the other hand is a protocol. And you can not cast a class & protocol type together.
So, the error is exactly as it says.
Non-protocol, non-class type 'ResponseMappedClass' cannot be used within a protocol-constrained type
Something like this will work as both the types are protocol.
as! (DataResponse<Codable & Decodable>) -> Void

Related

Swift Generics Generic parameter could not be inferred

I'm trying to create a protocol that has a function with generic parameters.
protocol APIRequest {
static func fetchData<T: Codable>(completion: #escaping(T?, NetworkError?) -> Void)
}
then I have a struct that conforms the protocol
static func fetchData<Ztar: Codable>(completion: #escaping (Ztar?, NetworkError?) -> Void) {
let url = URLConstructor.url(scheme: "https", host: "swapi.dev" , path: "/api")
guard let url = url else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
completion(nil, NetworkError.badResponse)
return
}
do {
let decoder = JSONDecoder()
let object = try decoder.decode(Ztar.self, from: data)
completion(object, nil)
} catch {
print(error)
}
}
task.resume()
}
but I'm not sure if that implementation of the type of the generic is correct because in my ViewController I'm receiving the error Generic parameter 'Ztar' could not be inferred
NetworkManager.fetchData { star, error in
}
Can someone explain What I'm doing wrong?
It's not possible to implement this correctly. What type is Ztar? How would the compiler know? You've written "fetchData will fetch some kind of data, out of all the infinitely possible kinds of data in the universe, and you, the compiler, should decode it." That's not possible.
Instead, the way this would generally be written is:
fetchData<T: Codable>(ofType: T.Type, completion: #escaping (Result<T, NetworkError>) -> Void)
And then you would need to pass the type you expect as the first parameter:
fetchData(ofType: Record.self) { resultRecord in ... }
If you don't know what type Ztar is, how can the compiler?
(Note that you should almost never use (T?, Error?) as a type. That says "maybe T, maybe Error, maybe neither, maybe both." You almost always mean Result<T, Error> instead. That says "either T or Error.")

Why is this generic json function not working?

Looking to make a function to decode any Type that I pass in at the call site.. so of course it has to be generic over the type. So far my implementation looks like this:
func getAll<ResourceType>(_ type: ResourceType, using url: URL, completion: #escaping ([ResourceType] -> Void) where ResourceType: Codable {
//This works just fine
}
but when I try to call it:
getAll(Movie, using: URL(string: "movies.com")) { movies in
//This does not work
//Error says Type 'Movie.Type' cannot conform to 'Decodable`
//Error says Type 'Movie.Type' cannot conform to 'Encodable`
// This baffles me because in the func declaration it says *where RT: Codable*
}
type must be ResourceType.Type, the generic ResourceType can be both a single object and an array
func getAll<ResourceType>(_ type: ResourceType.Type, using url: URL, completion: #escaping (ResourceType) -> Void) where ResourceType: Codable {
and when calling the method type must be [Movie].self
getAll([Movie].self, using: URL(string: "movies.com")) { movies in
and a parentheses is missing in the signature of the method and the compiler with complain about an unwrapped URL

How to pass nil as optional generic function argument

I have a function as follows:
func request<D: Decodable>(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
expecting type: D.Type? = nil,
completion: #escaping (Result<D?>) -> Void)
Is it possible to do this: request(..., expecting: nil) or func request<D: Decodable>(... expecting type: D.Type? = nil) ?
I'm thinking I've reached limitations to how generics can be used because when I do this I get compile errors that have absolutely nothing to do with the code I'm working on so I think the compiler might be getting confused.
When I use the function, such as: request(from: "https:..", requestType: .post, body: body), the compiler complains that Enum element 'post' cannot be referenced as an instance member
Some of my API requests don't return anything in the body so I'm trying to find a way to express that using this generic function I've set up
The underlying problem here is that the type you want is Void, but Void is not Decodable, and you can't make it Decodable because non-nominal types (like Void) can't be extended. This is just a current limitation of Swift.
The right solution to this is overloading. Create two methods:
// For values
func request<D: Decodable>(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
expecting type: D.Type,
completion: #escaping (Result<D>) -> Void) {}
// For non-values
func request(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
completion: #escaping (Error?) -> Void) {}
Create another shared method that turns a request into Data and that both can call:
func requestData(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
completion: #escaping (Result<Data>) -> Void) {}
Your decoding request function will now convert .success(Data) into a D. Your non-decoding request function will throw away the data (or possibly ensure that it is empty if you're pedantic about it), and call the completion handler.
If you wanted your code to be a little more parallel, so that it always passes a Result rather than an Error?, then you can still have that with a tweak to the signature:
func request(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
completion: #escaping (Result<Void>) -> Void) {}
But overloading is still the answer here.
(OLD ANSWERS)
There's no problem with passing nil here, as long as D can somehow be inferred. But there has to be a way to infer D. For example, the following should be fine:
request(from: "") { (result: Result<Bool?>) in
print(result)
}
What would not be fine would be this:
request(from: "") { (result) in
print(result)
}
Because in that case, there's no way to determine what D is.
That said, given your goal, you don't want Type to be optional anyway. As you say, sometimes the result is "returns nothing." The correct type for "returns nothing" is Void, not nil.
func request<D: Decodable>(from urlString: String,
useToken: Bool = false,
body: Data? = nil,
expecting type: D.Type = Void.self, // <<----
completion: #escaping (Result<D>) -> Void)
(I'm assuming you then want Result<D> rather than Result<D?>, but either could be correct depending on your precise use case.)
Void is a normal type in Swift. It is a type with exactly one value: (), the empty tuple.
this works fine for me in playground
let t = testStruct.init()
let t2 : testStruct? = nil
test(t)
testOptional(t)
testOptional(t2)
func test<T: testProtocol>(_ para: T){
print(para.id())
}
func testOptional<T: testProtocol>(_ para: T?){
if let p = para{
print(p.id())
}
}
protocol testProtocol {
func id() -> String
}
struct testStruct{
}
extension testStruct : testProtocol {
func id() -> String {
return "hello"
}
}
but you can't just call testOptional(). it has to be passed something, even a nil optional so the type can be inferred.

Can't infer generic type on static function with completion block

I have a static function that uses generics, but I can't get it to infer the generic type when it's called. The function:
static func getDocument<T: JSONDecodable>(_ document: String, fromCollection collection: FirebaseStorage.FirestoreCollections, completion: #escaping (_ decodedDoc: T?, _ error: Error?) -> ()) {
let docRef = firestore.collection(collection.rawValue).document(document)
docRef.getDocument { documentSnapshot, error in
guard error == nil,
let docData = documentSnapshot?.data(),
let decodedDoc = T(json: docData) else {
completion(nil, error)
return
}
completion(decodedDoc, nil)
}
}
Called using:
FirebaseClient.getDocument(
id,
fromCollection: FirebaseStorage.FirestoreCollections.users) { (profile, error) in
}
This gives the error: Generic parameter 'T' could not be inferred. How can I make the generic part of the function work?
FirebaseClient.getDocument(
id,
fromCollection: FirebaseStorage.FirestoreCollections.users) { (profile: ProfileType?, error) in
}
You'll need to let Swift know what type profile is where I've added ProfileType. That should do it!
Kane's answer is good, but a more flexible approach is to pass the type directly. For example, this makes it possible to have an optional completion handler, or to ignore the parameter with _ if you don't care about it. (That said, this approach is a little longer to type, so sometimes Kane's way is better.)
static func getDocument<T: JSONDecodable>(_ document: String,
ofType: T.Type,
completion: #escaping (_ decodedDoc: T?, _ error: Error?) -> ())
This makes everything explicit. You call it this way:
FirebaseClient.getDocument(id, ofType: ProfileType.self) { (profile, error) in ... }
Note that there's no need to use the ofType parameter for anything. It's just there to specialize the generic.
This is pretty close to how Decodable works, and is applicable to a lot of problems. But Kane's solution is also handy at times if it's more convenient.

Cannot invoke function even when all parameters are correct

I'm trying to invoke this function, where DataObject is a protocol that User uses:
static func makePostRequest<T:DataObject>(to endpoint:String, dataObject: T, objectType: T.Type, completionHandler: #escaping (DataObject?, Error?) -> Void) {
//some code
}
But I can't invoke it with this parameters:
static func create<T : DataObject>(_ object: T, completionHandler: #escaping (DataObject?, Error?) -> Void) {
let endpoint = NetworkManager.shared.baseURL + UserDAO.methodPath
NetworkManager.makePostRequest(to: endpoint, dataObject: object, objectType: User.self, completionHandler: completionHandler)
}
An error appears to me saying I can't invoke it with this list of parameters, but all of them are correct... I really don't know what's happening.
Error:
Cannot invoke 'makePostRequest' with an argument list of type '(to: String, dataObject: T, objectType: User.Type, completionHandler: (DataObject?, Error?) -> Void)'
I guess you need to call it like this, at least it worked for me in playground.
makePostRequest(to: endpoint, dataObject: object, objectType: T.self, completionHandler: completionHandler)
or add other generic variable
func makePostRequest<T: DataObject, U>(to endpoint:String, dataObject: T, objectType: U.Type, completionHandler: #escaping (DataObject?, Error?) -> Void)
I do not know what are you trying to achieve, so this might not help you at all