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.
Related
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
The code below does not compile
Cannot call value of non-function type 'Bool'
Is is possible to distinguish between a method closure argument and local variable when both have the same name?
func methodA(message: String, success: #escaping () -> Void) {
let success = true
if success {
print(message)
}
success()
}
You cannot do that because all the vars you pass to the function are function's vars, so there is no way to distinguish between success function to success var.
More swift way is to change the name to completion and return a bool var indicate success or fail like that:
func methodA(message: String, completion: #escaping (_ success: Bool) -> Void) {
let success = true
if success {
print(message)
}
completion(success)
}
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
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.
So what I want to have is a class that may get a closure passed to it in a function, it may also at some point want to disregard a that closure. How can I check if the closure variable is set and hwo can I delete it when I am done with it?
Cannot invoke '!=' with an argument list of type '(#lvalue (sucsess:
Bool!, products: [AnyObject]!) -> ()?, NilLiteralConvertible)' Type
'(sucsess: Bool!, products: [AnyObject]!) -> ()?' does not conform to
protocol 'NilLiteralConvertible'
class someClass{
//typealias completionHandlerClosureType = (sucsess:Bool!, items:[AnyObject]!)->()
var completionHandler:(sucsess:Bool!, items:[AnyObject]!)->()?
var hitpoints = 100
var someset = ["oh no!","avenge me!"]
init(){}
func getHitFunc(impact:Int, passedCompletionsHandler:(sucsess:Bool!, items:[AnyObject]!)->()){
completionHandler = passedCompletionsHandler
hitpoints = hitpoints - impact
}
func checkIfDead{
if hitpoints<=0 { // The error received
if completionHandler != nil{// Cannot invoke '!=' with an argument list of type
//'(#lvalue (sucsess: Bool!, products: [AnyObject]!) -> ()?, NilLiteralConvertible)'
//run the handler if dead
completionHandler(sucsess: true, items: someset)
//do not run it again
completionHandler = nil //Type '(sucsess: Bool!, products: [AnyObject]!) -> ()?' does not conform to protocol 'NilLiteralConvertible'
}
}
else{
completionHandler = nil //Type '(sucsess: Bool!, products: [AnyObject]!) -> ()?' does not conform to protocol 'NilLiteralConvertible'
}
}
}
You need to wrap your closure signature in parentheses to make the closure itself optional. The way it's written now, the closure returns an optional Void (which doesn't really make sense).
var completionHandler: ((sucsess:Bool!, items:[AnyObject]!)->())?
Some style points and revisions to your example code:
// Capitalize class names so it's clear what's a class
class SomeClass {
// "success" has two "c"s
var completionHandler: ((success:Bool!, items:[AnyObject]!)->())?
var hitpoints = 100
var someset = ["oh no!","avenge me!"]
init() { }
func getHitFunc(impact:Int, passedCompletionsHandler:(success:Bool!, items:[AnyObject]!)->()){
completionHandler = passedCompletionsHandler
hitpoints = hitpoints - impact
}
// You were missing the argument list here:
func checkIfDead() {
if hitpoints <= 0 {
// Rather than checking to see if the completion handler exists, you can
// just call it using optional syntax like this:
completionHandler?(success: true, items: someset)
}
completionHandler = nil
}
}
First, in your declaration of the completion handler, you need to declare the whole thing as optional with the use of parentheses:
var completionHandler: ((_ success: Bool, _ items: [Any]?) -> ())?
Or, perhaps better, you can replace that final () with Void:
var completionHandler: ((_ success: Bool, _ items: [Any]?) -> Void)?
Also, note, I don't think you meant to make the Bool optional (because if the closure exists, you presumably always pass a success value of true or false). Clearly, the array of items might well be optional.
Anyway, when done, you'd just make sure to unwrap that optional:
func checkIfDead() {
if hitpoints <= 0 {
completionHandler?(true, items)
}
completionHandler = nil
}
This performs the closure if and only if it is not nil, avoiding the need to explicitly check if it was nil.
For what it's worth, this might be a case where your typealias might make this less confusing:
typealias CompletionHandlerClosureType = (_ success: Bool, _ items: [Any]?) -> Void
Then the property is simply:
var completionHandler: CompletionHandlerClosureType?
The function that takes this completionHandler as a optional parameter could do:
func startSomeProcess(passedCompletionHandler: CompletionHandlerClosureType?) {
completionHandler = passedCompletionHandler
// do whatever else you want
}
and then the final completion logic is unchanged:
func finishSomeProcess() {
completionHandler?(true, items)
completionHandler = nil
}
(Note, the above has been modified for Swift 3. Please see previous revision of this answer if you want to see Swift 2 renditions.)