Why is this generic json function not working? - swift

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

Related

Generic parameter 'T' could not be inferred when passing Codable struct

Please go through the code first for better understanding.
func getType<T: Decodable>(urlString: String, header: HTTPHeaders, completion: #escaping (T?) -> Void){
}
let networkManager = DataManager()
networkManager.getType(urlString: kGetMyDayTaskDetails + strId, header: header) { (MyDayAndTaskDetails) in
}
Error:
Generic parameter 'T' could not be inferred
I am trying to pass the struct as parameter using Generics to use a common method for api calling. But not getting how to call correctly. Please guide.
Consider that the parameter in the closure is an instance, not a type. For example like
networkManager.getType(urlString: kGetMyDayTaskDetails + strId,
header: header) { details in
}
To specify the generic type annotate it and according to the signature it must be optional
networkManager.getType(urlString: kGetMyDayTaskDetails + strId,
header: header) { (details : MyDayAndTaskDetails?) in
}

Swift inferring a completion handler closure to be the default #nonescaping instead of #escaping when completion handler explicitly uses #escaping

Swift 4.2, Xcode 10.1
In the order processing app I'm working on, the user may do a search for orders already processed or submitted. When that happens, it will check to see if it has a cache of orders, and if it does not, it will refill that cache using an asynchronous API request, then check the cache again.
The function that refills the cache is a private static one that accepts an escaping completion handler. Whenever I have used that completion handler in the past, all I had to do was add a closure at the end of the function call. This was before I was instructed to make a cache of all data wherever possible, and only use the API to refill that cache. Since then, the function has become private, because there will never be a need to call the API directly from anywhere but within this class.
Now, when I put the closure directly after the function call, it's giving me an error that basically says I'm passing a #nonescaping closure instead of an #escaping closure:
"Cannot invoke 'getAndCacheAPIData' with an argument list of type '(type: Codable.Type, (String?) -> Void)', Expected an argument list of type '(type: CodableClass.Type, #escaping (String?) -> Void)'"
I've never had to explicitly declare a closure to be #escaping before, nether does it seem to be possible. I suspect that because the function is both private AND static, there's some kind of issue happening with the way closures are inferred to be #escaping. I'm out of my depth. I could try converting the static class to a singleton, but I'm hesitant to refactor a bunch of working code because of one error until I'm absolutely sure that change will resolve the issue, and that what I'm trying to do isn't possible unless I change my approach.
Here's the code:
public static func fillSearchResultArray<ManagedClass: NSManagedObject>(query:String, parameters:[String], with type: ManagedClass.Type, completionHandler: #escaping (String?)->Void)
{
let codableType:Codable.Type
switch type
{
case is ClientTable.Type:
codableType = ClientData.self
case is OrderTable.Type:
codableType = OrderData.self
case is ProductTable.Type:
codableType = ProductData.self
default:
completionHandler("Unrecognized type.")
return
}
let fetchedData:[ManagedClass]
do
{
fetchedData = try PersistenceManager.shared.fetch(ManagedClass.self)
}
catch
{
completionHandler(error.localizedDescription)
return
}
if fetchedData.isEmpty
{
AppNetwork.getAndCacheAPIData(type: codableType)//error here
{(firstErrorString) in
//move search array data to the cache
if firstErrorString.exists
{
completionHandler(error)
}
else
{
AppNetwork.fillSearchResultArray(query: query, parameters: parameters, type: type)
{ errorString in
completionHandler(errorString)
}
}
}
return
}
else
{ ...
The signature of the function being called:
private static func getAndCacheAPIData <CodableClass: Any & Codable>(type:CodableClass.Type, completionHandler: #escaping (String?)->Void)
Why is swift inferring this closure to be the default #nonescaping when before it always inferred it to be #escaping?
The problem has nothing to do with the closure, or static, or private. It has to do with the type parameter. You cannot call this method:
private static func getAndCacheAPIData <CodableClass: Any & Codable>(type:CodableClass.Type, completionHandler: #escaping (String?)->Void)
with a variable of type Codable.Type. The type value you pass must be a concrete type, known at compile-time. If you want to pass a variable, you can't use a generic. It would have to be:
private static func getAndCacheAPIData(type: Codable.Type, completionHandler: #escaping (String?)->Void)
Alternately, you can call this as:
AppNetwork.getAndCacheAPIData(type: Int.self) {(firstErrorString) in ... }
or some other known-at-compile-time type.
Probably what you really want here is something like:
let completion: (String?) -> Void = {(firstErrorString) in ... }
switch ... {
case ...:
AppNetwork.getAndCacheAPIData(type: Int.self, completion: completion)
case ...:
AppNetwork.getAndCacheAPIData(type: String.self, completion: completion)
...
The basic problem is that protocols do not conform to themselves, so a variable of type Codable.Type does not satisfy the : Codable requirement. This comes down to the same reason you can't just call:
AppNetwork.getAndCacheAPIData(type: Codable.self) {...}
Alternately, you could refactor it this way:
private static func handleAPI<CodableClass: Codable>(type: CodableClass.Type) {
getAndCacheAPIData(type: type.self) { _ in ... the completion handler ..}
}
switch ... {
case ...:
AppNetwork.handleAPI(type: Int.self)
case ...:
AppNetwork.handleAPI(type: String.self)
...
Side note: Any & is meaningless here. You just meant <CodableClass: Codable>.

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.

Swift 4: cast issue with Generics

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

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.