Swift Generics - Generic parameter 'T' could not be inferred - swift

I am trying to develop a generic API fetch method but I am getting above compilation error. After several hours of trying I am still not able to fix it.
Type Alias
typealias RealmResponse<T> = (Int, Results<T>?, ErrorResponse?) where T:Object, T:Mappable
Class Declaration
class Profile:Object, Mappable{
}
API Fetch Method
#discardableResult
func requestRealmObject<T>(endPoint:String,
disposeBag:DisposeBag,
method:HTTPMethod = .get,
query:[String:Any] = [:],
params:[String:Any] = [:]) -> Observable<RealmResponse<T>> where T:Object, T:Mappable{
let realmResponse:Observable<ObjectResponse<T>> = APIManager.requestObject(endpoint: endPoint,
method: method,
query: query,
params: params)
.shareReplay(1)
realmResponse
.filter{ 200..<300 ~= $0.0 }
.map{ response:ObjectResponse<Profile> -> [Profile] in
if let profile = response.1{
return [profile]
}
return []
}
.subscribe(APIManager.shared.realm.rx.add(update: true, onError: nil))
.disposed(by: disposeBag)
let realmObjects = APIManager.shared.realm.objects(T.self)
return Observable.zip(realmResponse, Observable.collection(from: realmObjects)){ (response, results) in
return (response.0, results, response.2)
}
}
Usage
let profileObserver:Observable<RealmResponse<Profile>> = requestRealmObject(endpoint: APIManager.userProfileEndPoint,
disposeBag:disposeBag,
method: .post,
query:[:],
params:["username":loggedInUser.value])
.shareReplay(1)
Any help in this regard would be highly appreciated.

Related

Alamofire, HTTPheaders for post request [string:any]

I need to send a post request using alamofire to my server, one of the header to be sent is not a string value but is an Int
Reading the documentation of Alamofire look like the HTTPHeaders is only type [String: String]
Is there any way to customise the HTTPHeaders to [String:Any]?
I can't find to much understandable for me online.
thanks
Alamofire doesn't have such methods, but you can easily do it
["hey": 1].mapValues { String(describing: $0) } returns [String: String]
If you have many places where you're using it, you can:
Create extension for Dictionary
extension Dictionary where Key == String, Value == Any {
func toHTTPHeaders() -> HTTPHeaders {
HTTPHeaders(mapValues { String(describing: $0) })
}
}
// Usage
AF.request(URL(fileURLWithPath: ""), headers: ["": 1].toHTTPHeaders())
Create extension for HTTPHeaders
extension HTTPHeaders: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, Any)...) {
self.init()
elements.forEach { update(name: $0.0, value: String(describing: $0.1)) }
}
}
// usage
AF.request(URL(fileURLWithPath: ""), headers: HTTPHeaders(["": 1]))
Create extension for Session
extension Session {
open func request(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: [String: Any],
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil) -> DataRequest {
return request(convertible, method: method, parameters: parameters, encoding: encoding, headers: headers.mapValues { String(describing: $0) }, interceptor: interceptor, requestModifier: requestModifier)
}
}
// Usage
AF.request(URL(fileURLWithPath: ""), headers: ["": 1])
The reason there's no such option in Alamofire is type safety. When you use Any you can literary pass any value there and so probability of a mistake is much more. By requiring string library makes sure you're converting all values you need by yourself.
I'd go for the first variant, because it's more clear when you read the code that there's something going on there

Swift Combine: Cannot refactor repetitive code

My API returns this format, where data can contain all kinds of responses.
{
status: // http status
error?: // error handle
data?: // your response data
meta?: // meta data, eg. pagination
debug?: // debuging infos
}
I have made a Codable Response type with a generic for the optional data, of which we do not know the type.
struct MyResponse<T: Codable>: Codable {
let status: Int
let error: String?
let data: T?
let meta: Paging?
let debug: String?
}
I am now trying to write API convenience methods as concisely as possible. So I have a function to return a generic publisher that I can use for all these responses, i.e. one that pre-parses the response and catches any errors upfront.
First, I get a dataTaskPublisher that processes the parameter inputs, if any. Endpoint is just a convenience String enum for my endpoints, Method is similar. MyRequest returns a URLRequest with some necessary headers etc.
Notice the way I define the parameters: params: [String:T]. This is standard JSON so it could be strings, numbers etc.
It seems this T is the problem somehow..
static fileprivate func publisher<T: Encodable>(
_ path: Endpoint,
method: Method,
params: [String:T] = [:]) throws
-> URLSession.DataTaskPublisher
{
let url = API.baseURL.appendingPathComponent(path.rawValue)
var request = API.MyRequest(url: url)
if method == .POST && params.count > 0 {
request.httpMethod = method.rawValue
do {
let data = try JSONEncoder().encode(params)
request.httpBody = data
return URLSession.shared.dataTaskPublisher(for: request)
}
catch let err {
throw MyError.encoding(description: String(describing: err))
}
}
return URLSession.shared.dataTaskPublisher(for: request)
}
Next, I am parsing the response.
static func myPublisher<T: Encodable, R: Decodable>(
_ path: Endpoint,
method: Method = .GET,
params: [String:T] = [:])
-> AnyPublisher<MyResponse<R>, MyError>
{
do {
return try publisher(path, method: method, params: params)
.map(\.data)
.mapError { MyError.network(description: "\($0)")}
.decode(type: MyResponse<R>.self, decoder: self.agent.decoder)
.mapError { MyError.encoding(description: "\($0)")} //(2)
.tryMap {
if $0.status > 204 {
throw MyError.network(description: "\($0.status): \($0.error!)")
}
else {
return $0 // returns a MyResponse
}
}
.mapError { $0 as! MyError }
//(1)
.eraseToAnyPublisher()
}
catch let err {
return Fail<MyResponse<R>,MyError>(error: err as? MyError ??
MyError.undefined(description: "\(err)"))
.eraseToAnyPublisher()
}
}
Now I can write an endpoint method easily. Here are two examples.
static func documents() -> AnyPublisher<[Document], MyError> {
return myPublisher(.documents)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() as AnyPublisher<[Document], MyError>
}
and
static func user() -> AnyPublisher<User, MyError> {
return myPublisher(.user)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() as AnyPublisher<User, MyError>
}
All this is working well. Please note that each time, I have to specify my exact return type twice. I think I can live with that.
I should be able to simplify this so that I do not have to repeat the same three operators (map, mapError, receive) in exactly the same way each time.
But when I insert .map(\.data!) at the location //(1) above I get the error Generic parameter T could not be inferred. at the location //(2).
This is really confusing. Why does the generic type in the input parameters play any role here? This must be related to the call to the .decode operator just above, where the generic in question is called R, not T.
Can you explain this? How can I refactor these operators upstream?
This code has a number of small problems. You're right that one is [String: T]. That means that for a given set of parameters, all the values must be of the same type. That's not "JSON." This will accept a [String: String] or a [String: Int], but you can't have both Int and String values in the same dictionary if you do it this way. And it will also accept [String: Document], and it doesn't seem like you really want that.
I'd recommend switching this to just Encodable, which would allow you to pass structs if that were convenient, or Dictionaries if that were convenient:
func publisher<Params: Encodable>(
_ path: Endpoint,
method: Method,
params: Params?) throws
-> URLSession.DataTaskPublisher
func myPublisher<Params: Encodable, R: Decodable>(
_ path: Endpoint,
method: Method = .GET,
params: Params?)
-> AnyPublisher<MyResponse<R>, MyError>
Then modify your params.count to check for nil instead.
Note that I didn't make params = nil a default parameter. That's because this would recreate a second problem you have. T (and Params) can't be inferred in the default case. For = [:], what is T? Swift has to know, even though it's empty. So instead of a default, you use an overload:
func myPublisher<R: Decodable>(
_ path: Endpoint,
method: Method = .GET)
-> AnyPublisher<MyResponse<R>, MyError> {
let params: String? = nil // This should be `Never?`, see https://twitter.com/cocoaphony/status/1184470123899478017
return myPublisher(path, method: method, params: params)
}
Now, when you don't pass any parameters, Params automatically becomes String.
So now your code is fine, and you don't need the as at the end
func documents() -> AnyPublisher<[Document], MyError> {
myPublisher(.documents)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() // <== Removed `as ...`
}
Now, that .map(\.data!) makes me sad. If you get back corrupt data from the server, the app will crash. There are lots of good reasons to crash apps; bad server data is never one of them. But fixing that isn't really related to this question (and is a little bit complicated because Failure types other than Error make things hard currently), so I'll leave it for now. My general recommendation is to use Error as your Failure type, and allow unexpected errors to just bubble up rather than wrapping them in an .undefined case. If you need some catch-all "other" anyway, you might as well do that with types ("is") rather than an extra enum case (which just moves the "is" to a switch). At the very least, I would move the Error->MyError mapping as late as possible, which will make handling this much easier.
One more tweak to make later things a little more general, I suspect MyResponse only needs to be Decodable, not Encodable (the rest of this works either way, but it makes it a little more flexible):
struct MyResponse<T: Decodable>: Decodable { ... }
And to your original question of how to make this reusable, you can now pull out a generic function:
func fetch<DataType, Params>(_: DataType.Type,
from endpoint: Endpoint,
method: Method = .GET,
params: Params?) -> AnyPublisher<DataType, MyError>
where DataType: Decodable, Params: Encodable
{
myPublisher(endpoint, method: method, params: params)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
// Overload to handle no parameters
func fetch<DataType>(_ dataType: DataType.Type,
from endpoint: Endpoint,
method: Method = .GET) -> AnyPublisher<DataType, MyError>
where DataType: Decodable
{
fetch(dataType, from: endpoint, method: method, params: nil as String?)
}
func documents() -> AnyPublisher<[Document], MyError> {
fetch([Document].self, from: .documents)
}

Swift error while returning multiple values from function

I can not understand what I did wrong
I have an app which loads posts and its comments. The view controller requests a function from an another file, which returns back (response?, comments?)
I get one error:
Initializer for conditional binding must have Optional type, not '(ActionResult?, [PostComment]?)'
For the line
if let (response, comments) = (response, comments )
What did I wrong?
commentsViewController
postComments.loadCommentForPost(id: postId) { (response, comments) in
// ERROR here: Initializer for conditional binding must have Optional type, not '(ActionResult?, [WorldMessageComment]?)'
if let (response, comments) = (response, comments ) {
if response!.success == 1 {
DispatchQueue.main.async(execute: {() -> Void in
self.comments = comments!
self.tableView.reloadData()
})
} else {
DispatchQueue.main.async(execute: {() -> Void in
self.handleResponses.displayError(title: response!.title, message: response!.message)
})
}
}
}
commentFunctions
func loadCommentsForPost(id: Int, completion: #escaping ((ActionResult?), [PostComment]?)->()){
// downloading the data and then
let comments : [PostComment] = ...
// Succesful
return completion((ActionResult(success: 1, title: responseTitle, message: responseMessage)), comments)
}
The issue is in the line:
if let (response, comments) = (response, comments ) {
What you are basically doing is creating a new non optional tuple on the right hand side of the assignment (with two optional components), so the compilator complains that you can't use if let with a non optional type.
You can in fact consider that the tuple return by loadCommentsForPost is already "split" in the arguments of the callback, so you can handle response and comments separately.
postComments.loadCommentForPost(id: postId) { response, comments in
if let response = response, let comments = comments {
if response.success == 1 {
...

Swift 4 generics with struct

I'm trying to use generics with Codable protocol, but I'm getting an error.
Cannot invoke 'decode' with an argument list of type '([T.Type], from: Data)
static func getRequest<T>(model: T.Type, url: String, parameters: [String: Any]? = nil, headers: [String: String]? = nil, data: #escaping (Any?, Any?, Error?) -> ()) -> Alamofire.DataRequest {
return Alamofire.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers)
.validate(contentType: [RequestHelper.HeaderKeys.contentTypeJson])
.responseJSON { (response) in
print(response)
switch response.result {
case .success:
let responseData = response.data!
do {
print(model)
print(T.self)
let decodable = try JSONDecoder().decode([model].self, from: responseData)
data(response.response?.allHeaderFields, decodable, nil)
} catch let error {
data(nil, nil, error)
}
case .failure(let requestError):
data(nil, nil, requestError)
print(requestError)
}
}
}
I need pass my struct model to this method
How can I fix this? Does anyone could help me?
decode() can only take a type that is Decodable. You need to specify that in your method signature. Either add where T: Decodable at the end of getRequest's declaration or just put <T: Decodable> inside the brackets to restrict T to decodable types only, and then you should be able to pass your parameter to decode().
EDIT: Looking at your code, there's another error:
let decodable = try JSONDecoder().decode([model].self, from: responseData)
Instead of [model].self, you need to pass [T].self. Otherwise you're passing an array of types rather than the type of an array.

Is there anyway I can avoid the use of what appears to be some "boilerplate" code?

I am starting with a function that looks like this:
func getUser(command: APICommand, id: Int, handler: (apiResponse: APIResponse<User>) -> Void ) {
let url = apiPath + "users/\(id)"
Alamofire.request(.GET, url, parameters: requestParameters)
.responseObject { (a:NSURLRequest, b:NSHTTPURLResponse?, c:User?, d:AnyObject?, e:NSError? ) in
let apiResponse = APIResponse(command: command,request: a, response: b, swiftObject: c, rawObject: d, error: e)
if AlamofireAPIRequestRepository.resultIsRetryableError(e, command: command) {
println("retrying request")
command.retry()
} else {
handler(apiResponse: apiResponse)
}
}
}
I am going to have a number of functions that look very similar such as, getUserList() for example.
Looking at this I realized the entire Alamofire.request call is going to be pretty much boiler plate code. The only difference will be the Type of argument c passed into the closure that gets called by the responseObject() method. In this case it is User? , in the getUserList() method it will be UserList?
Is there any way I can make this more generic and avoid what appears to be just "boilerplate" code?
I Here is what I have tried.
func alamofireGetRequest<T>(url: URLStringConvertible, parameters: [String: AnyObject]?,
command: APICommand, handler: (apiResponse: APIResponse<T>) -> Void) -> Void {
Alamofire.request(.GET, url, parameters: parameters)
.responseObject { (a:NSURLRequest, b:NSHTTPURLResponse?, c:T?, d:AnyObject?, e:NSError? ) in
let apiResponse = APIResponse(command: command,request: a, response: b, swiftObject: c, rawObject: d, error: e)
if AlamofireAPIRequestRepository.resultIsRetryableError(e, command: command) {
println("retrying request")
command.retry()
} else {
handler(apiResponse: apiResponse)
}
}
}
but the compiler complains with:
Cannot invoke 'responseObject' with an argument list of type
'((NSURLRequest, NSHTTPURLResponse?, T?, AnyObject?, NSError?) -> _)'
and if I replace the c:T? above with c:User? it is happy.
A comment below referred to this question, which may explain why the solution I tried does not work, but does really answer my intended question as to how to avoid this duplicated code.
I think I found an acceptable solution.
Below is the relevant parts of the code, but basically I end up passing in an argument type of AnyObject in to the closure rather than a generic. Before you say that I am cheating you have to understand that my original call would have looked something like this.
func testGetUserRequestSuccess() {
let getUserCommand=commandFactory.createGetUserCommandUsing(apiRepo, id: 1) {
if let swiftObject = $0.swiftObject {
XCTAssertEqual(swiftObject.id!, 1, "id is correct")
}
self.expectation.fulfill()
}
commandProcessor.processCommand(getUserCommand)
wait(5)
}
now it looks like this:
func testGetUserRequestSuccess() {
let getUserCommand=commandFactory.createGetUserCommandUsing(apiRepo, id: 1) {
if let swiftObject = $0.swiftObject as? User {
XCTAssertEqual(swiftObject.id!, 1, "id is correct")
}
self.expectation.fulfill()
}
commandProcessor.processCommand(getUserCommand)
wait(5)
}
so I had to add the as? User cast to the if let statement, which seems reasonable.
relevant code for anyone interested.
The "specialty" code (what will be repeated for each operation)
func getUser(retriesUse retryProcessor: APICommandProcessor, command: APICommand, id: Int, handler: (apiResponse: APIResponse) -> Void ) {
let url = apiPath + "users/\(id)"
Alamofire.request(.GET, url, parameters: requestParameters)
.responseObject { (a:NSURLRequest, b:NSHTTPURLResponse?, c:User?, d:AnyObject?, e:NSError? ) in
let apiResponse = APIResponse(command: command,request: a, response: b, swiftObject: c, rawObject: d, error: e)
self.finishResponse(command, retryProcessor: retryProcessor, apiResponse: apiResponse, handler: handler)
}
}
The common part that I wanted to factor out (mainly a placeholder at this point)
func finishResponse(command: APICommand, retryProcessor: APICommandProcessor, apiResponse: APIResponse, handler: (apiResponse: APIResponse) -> Void) -> Void {
if AlamofireAPIRequestRepository.resultIsRetryableError(apiResponse.error, retryProcessor: retryProcessor, command: command) {
println("retrying \(command)")
retryProcessor.retryCommand(command)
} else {
println("request completed")
handler(apiResponse: apiResponse)
}
}
a supporting struct used above:
struct APIResponse {
let command: APICommand
let request: NSURLRequest
let response: NSHTTPURLResponse?
let swiftObject: AnyObject?
let rawObject: AnyObject?
let error: NSError?
}