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
Related
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.
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.
Please don't close because of duplicate of "Ambiguous use of...". Even with intense research I was not able to find any similar threads solving my issue.
I'm updating a project to Swift 3 and am stuck at a compiler error:
Ambiguous use of 'authorize(_:completion:)'
Code:
func connectToInstagram() {
let auth: NSMutableDictionary = ["client_id": INSTAGRAM_CLIENT_ID,
SimpleAuthRedirectURIKey: INSTAGRAM_REDIRECT_URI]
SimpleAuth.configuration()["instagram"] = auth
SimpleAuth.authorize("instagram") { (anyObject, error) in // error here
if anyObject != nil {...
SimpleAuth is a Framework to handle Social Media authentication written in Objective C.
SimpleAuth:
open class SimpleAuth : NSObject {
open class func authorize(_ provider: String!, completion: SimpleAuth.SimpleAuthRequestHandler!)
SimpleAuthRequestHandler:
public typealias SimpleAuthRequestHandler = (Any?, Error?) -> Swift.Void
public let SimpleAuthPresentInterfaceBlockKey: String
public let SimpleAuthDismissInterfaceBlockKey: String
I have tried to change the line to:
_ = SimpleAuth.authorize("instagram") { (anyObject: Any?, error: Error?) in
_ = SimpleAuth.authorize("instagram", completion: { (anyObject: Any?, error: Error?) in
But just as expected, it didn't changed anything. What am I missing? Help is very appreciated.
Build Log:
xy/InstagramVC.swift:409:9: error: ambiguous use of 'authorize(_:completion:)'
SimpleAuth.authorize("instagram") { (any: Any?, error: Error?) -> Swift.Void in
^
SimpleAuth.SimpleAuth:24:21: note: found this candidate
open class func authorize(_ provider: String!, completion: SimpleAuth.SimpleAuthRequestHandler!)
^
SimpleAuth.SimpleAuth:34:21: note: found this candidate
open class func authorize(_ provider: String!, options: [AnyHashable : Any]! = [:], completion: SimpleAuth.SimpleAuthRequestHandler!)
The problem is with the API. They provide these two functions:
open class func authorize(_ provider: String!, completion: SimpleAuth.SimpleAuthRequestHandler!)
and
open class func authorize(_ provider: String!, options: [AnyHashable : Any]! = [:], completion: SimpleAuth.SimpleAuthRequestHandler!)
but as you can see the options argument of the second function has been given a default argument ([:]). So when you don't specify the options parameter, the compiler can't tell which function you want to call.
This means the first function can't be used. You'll always have to use the second function and pass an explicit options argument. So:
authorize("instagram", options: [:]) { (any: Any?, error: Error?) -> Void in ... }
It's probably worth filing a bug with the SimpleAuth framework author(s).
I am trying to get authorisation to use the addressbook using the CNContactStore function requestAccessForEntityType but I get an error I don't understand.
The function is defined in the class as:
public func requestAccessForEntityType(entityType: CNEntityType, completionHandler: (Bool, NSError?) -> Void)
This is my code:
let addressBookRef: CNContactStore = CNContactStore.requestAccessForEntityType(CNEntityType.Contacts, completionHandler: authorizationHandler)
func authorizationHandler(granted: Bool, error: NSError?) {
}
Compiler error:
Extra argument 'completionHandler' in call
Turns out that I was defining the property directly in the class. Obviously you can't run a function there and so it was not working. Duh!
All I need to do was put in the class:
let addressBookRef = CNContactStore()
And the following when it was time to actually ask for permission:
addressBookRef.requestAccessForEntityType(CNEntityType.Contacts, completionHandler: authorizationHandler)