I am studying networking (Alamofire).
And in his pet project on Viper architecture.
I am making a get request and getting a to-do list from a local server.
The data is returned to me successfully.
But I just can't figure out how to get them and transfer them to Interactor...
I want my fetchToDos method to return an array. But I keep making mistakes.
func fetchToDos() -> [ToDo]? { // <- My mistake is clearly here
let request = Session.default.request("http://localhost:3003/")
request.responseDecodable(of: ToDos.self) { (response) in
switch response.result {
case .success(let tasks):
print("SUCCESS to FETCH JSON: \(tasks)")
case .failure(let error):
print("FAILED to FETCH JSON: \(error.localizedDescription)")
}
}
}
you are using an asynchronous function, and so one way to get something out
of it when it is finished, is to use a completion handler, something like this:
(note you need to do the error checking etc...before you can use this for real)
class ToDoNetworking {
func fetchToDos(completion: #escaping ([ToDo] -> Void)) { // <- try this
let request = Session.default.request("http://localhost:3003/")
request.responseDecodable(of: [ToDos].self) { (response) in
switch response.result {
case .success(let tasks):
print("SUCCESS to FETCH JSON: \(tasks)")
completion(tasks) // <-- assuming tasks is [ToDo]
case .failure(let error):
print("FAILED to FETCH JSON: \(error.localizedDescription)")
completion([])
}
}
}
}
What is the type of data being returned by the network call? If it's an array of ToDo objects and your ToDo object supports Codable, then it's likely you want:
request.responseDecodable(of: [ToDo].self)
you pass in the type of object that you want to decode, which it sounds like, is an array of ToDo objects, hence [ToDo].self.
Related
I'm struggling with making badge requests with Alamofire and I need help.
I have some ids and with them I need to struct parameters (Dictionary String) and send a GET request with Alamofire. Everything is fine, but I need to cover the case when ids are above 200, because when they are more than 200, API returns 414 code status (too long URL). So when ids are more than 200 they are separated in chunks. With each chunk I'm making a new request to API. The problem is that I return only the first 200 ids when I call my method. Here is an example:
func request (_ idsDict: [String: [String]], _ idSchema: String, _ completion: #escaping Result<SomeModel, Error>) -> Void {
let chunks = transformEntitiesIdsToChunks(idsDict)
let decoder = JSONDecoder()
chunks.forEach {chunk in
let parameters = constructQueryParams(idsDict, chunk, idSchema, apiKey, clientId)
AF.request(baseURL, parameters: parameters).response { response in
switch response.result {
case .success(let data):
// some error handling for decoding and no data
completion(.success(data.data))
case .failure(let error):
return completion(.failure(error.localizedDescription))
}
}
}
}
// Method wraps AF request in a continuation block and makes sure that the closure from request method returned data or throwed error.
// That way fetching from API becomes async/await and can be used in do/try/catch block.
func getIdsEntities (_ idsDict: [String: [String]], _ idSchema: String) async throws -> [SomeModel] {
return try await withUnsafeThrowingContinuation { continuation in
request(idsDict, idSchema) { result in
switch result {
case .success(let data):
continuation.resume(returning: data)
return
case .failure(let error):
continuation.resume(throwing: error)
return
}
}
}
}
I have tried with recursive functions and with DispatchGroup but none of them worked. Any help will be appriciated. Thank you in advance.
Thanks to Larme's comment I was able to find my mistake. When making request to API I was passing the decoded response to the completion closure. To fix this I had to declare an array of model let responses:[SomeModel] = [] and append the decoded result to it. I used let group = DispatchGroup() so I can wait the requests to execute and have my final array of results and then I used group.notify(queue: .main, execute: {completion(.success(responses))}) to return to the main queue and have my array of completed fetched data. This is now how my code looks like:
private func request (_ idsDict: [String: [String]], _ idSchema: String, _ completion: #escaping APIListResponseClosure<SomeModel>) -> Void {
var responses: [SomeModel] = []
let group = DispatchGroup()
let chunks = transformEntitiesIdsToChunks(idsDict)
let decoder = JSONDecoder()
chunks.forEach {chunk in
group.enter()
let parameters = constructQueryParams(idsDict, chunk, idSchema, apiKey, clientId)
AF.request(baseURL, parameters: parameters).response { response in
switch response.result {
case .success(let data):
// some error handling for decoding and no data
responses.append(data.data)
group.leave()
case .failure(let error):
return completion(.failure(.APIError(error.localizedDescription)))
}
}
}
group.notify(queue: .main, execute: {
print("Ids are fetched")
completion(.success(responses))
})
}
Thanks again to Larme and I hope I helped someone else with this case.
I am trying to Apollo framework and a graphql api to obtain the data then return it. Once I have the data in another swift file, I want to call on certain parts of the data and assign it to a variable. The errors I get is variable used before it is initialized. and if try to return the variable from within the closure I get "Unexpected Non-Void Return Value In Void Function ". I heard of ways to get around that error but I don't completely understand it and how it works with my code. If you need more code or context you can message me and I can share my GitHub repo. Sorry if the code is bad, please don't roast me. I am still a beginner.
import Foundation
import Apollo
struct AniListAPI {
let aniListUrl = "https://graphql.anilist.co"
func ObtainData(AnimeID: Int)-> QueryQuery.Data{
var theData: QueryQuery.Data
let theInfo = QueryQuery(id: AnimeID)
GraphClient.fetch(query: theInfo) { result in
switch result {
case .failure(let error):
print("A big No no happened \(error)")
case .success(let GraphQLResult):
guard let Info = GraphQLResult.data else {return}
theData = Info
}
}
return theData
}
}
Unexpected Non-Void Return Value In Void Function.
The reason you're getting this warning is because you can't return value from inside the closure. Use closure instead of returning value.
func ObtainData(AnimeID: Int, completion: #escaping (Data) -> Void) {
var TheData: QueryQuery.Data
let TheInfo = QueryQuery(id: AnimeID)
GraphClient.fetch(query: TheInfo) { result in
switch result {
case .failure(let error):
print("A big no no happened retard \(error)")
case .success(let GraphQLResult):
guard let Info = GraphQLResult.data else {return}
TheData = Info
completion(TheData)
}
}
}
and call it like..
ObtainData(AnimeID: 123) { (anyData) in
print (anyData)
// continue your logic
}
I have this code in a ServiceClient. It handles service-level calls, like signIn(user, password, completion), listObjects(completion), addObject(objectID, content, completion), getObject(id, completion) etc. It contains (but doesn't subclass) an APIClient, which performs only basic HTTPS services like perform(request, completion).
I don't really want the controller that sits above this to deal with 404s as success, which means trapping the error in ServiceClient. So the idea is APIClient deals with networking errors whereas ServiceClient deals with unexpected HTTP results.
So I end up with this in ServiceClient, where errors like invalidURL are converted from an APIClient enum to a ServiceClient enum:
apiClient.perform(request) {result in
switch result {
case .success(let data):
guard data.statusCode == 200 else {
completion(.failure(.badResponse))
return
}
completion(.success(data))
case .failure(let error):
switch error {
case .invalidURL:
completion(.failure(.invalidURL))
case .requestFailed:
completion(.failure(.requestFailed))
case .decodingFailure:
completion(.failure(.decodingFailure))
}
}
}
I think in this case I'll just make APIClient handle invalid HTTP status codes, but what's the more general solution to this? At some point I'll want different error codes for different service clients, at which point this becomes a problem again.
I suggest using Int type enumeration for both ServiceClient and APIClient.
As I understood this is your custom enumerations.
So, assuming you have ServiceClientError and APIClientError you can implement them using this way:
enum ServiceClientError: Int {
case invalidURL, requestFailed, decodingFailure
}
enum APIClientError: Int {
case invalidURL, requestFailed, decodingFailure
}
You can create your custom conversion method:
extension ServiceClientError {
static func create(from apiClientError: APIClientError) -> ServiceClientError {
return ServiceClientError(rawValue: apiClientError.rawValue)
}
}
Wanted function:
apiClient.perform(request) {result in
switch result {
case .success(let data):
guard data.statusCode == 200 else {
completion(.failure(.badResponse))
return
}
completion(.success(data))
case .failure(let error):
guard let serviceClientError = ServiceClientError.create(from: error) else {
/// Handle incorrect behavior
fatalError("Wrong enumeration mapping")
return
}
completion(.failure(serviceClientError))
}
}
I used Alamofire and PromiseKit as separate Cocoapod installs. I can retrieve the JSON data using Alamofire, but I am receiving the error below when configuring PromiseKit. The error below appears in the line where 'fulfill, reject' are in.
Error message: Contextual closure type '(Resolver<_>) -> Void' expects 1 argument, but 2 were used in closure body
I am using Xcode 9.2 and IOS 11.2 inside of the Simulator. Thank you for your advice in advance!
func wantToReturnAnArrayOfActor() -> Promise<[Actor]> {
return Promise { fulfill, reject in
Alamofire.request(ApiUrl.url.rawValue).responseJSON { (response) in
switch(response.result)
{
case .success(let responseString): print("my response string = \(responseString)")
let actorResponse = ActorApiResponse(JSONString: "\(responseString)")//converts all of the data into the ActorApiResponse model class
return when(fulfilled: actorResponse)
DispatchQueue.main.async {
print("actorResponse = \(String(describing: actorResponse))")
}
case .failure(let error): print("alamofire error = \(error)")
}
}
}
}
Should it rather be like this,
func wantToReturnAnArrayOfActor() -> Promise<[Actor]> {
return Promise() { resolver in
Alamofire.request(ApiUrl.url.rawValue).responseJSON { (response) in
switch(response.result)
{
case .success(let responseObject):
let actorResponse = ActorApiResponse(jsonObject: responseObject)
let actors = actorResponse.getActors()
resolver.fulfill(actors)
case .failure(let error):
resolver.reject(error)
}
}
}
}
The initializer closure for Promise takes in single argument, which is of type Resolver, which is what your error says. Then, you would want to resolve your promise with result which is of type [Actor] when the promise execution is finished or then reject with error if error occurred during the execution.
Few points to note here:
Alamofire.request(_).responseJSON returns json object not json string.
If your ActorApiResponse is the object which transforms the json to [Actor], you should have proper method to convert json object to actual data type ie. [Actor].
You could have your ActorApiResponse something like this,
struct ActorApiResponse {
init(jsonObject: Any) {
}
func getActors() -> [Actor] {
// calculate and return actors
return []
}
}
Then, you can call it from else where,
wantToReturnAnArrayOfActor().done {
// do something with [Actor here]
// You can also chain the multiple promise using .then instead of using done
}.catch { error in
print("Error occurred \(error)")
}
I'm using a lot of async network request (btw any network request in iOS need to by async) and I'm finding way to better handle errors from Apple's dataTaskWithRequest which not supports throws.
I have code like that:
func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ()) {
let request = NSURLRequest(URL: NSURL(string: "http://google.com")!)
if someData == nil {
// throw my custom error
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
// here I want to handle Apple's error
}
task.resume()
}
I need to parse my possible custom errors and handle possible connection errors from dataTaskWithRequest. Swift 2 introduced throws, but you can't throw from Apple's closure because they have no throw support and running async.
I see only way to add to my completion block NSError returning, but as I know using NSError is old-style Objective-C way. ErrorType can be used only with throws (afaik).
What's the best and most modern method to handle error when using Apple network closures? There is no way no use throws in any async network functions as I understand?
there are many ways you can solve this, but i would recommend using a completion block which expects a Result Enum. this would probably be the most 'Swift' way.
the result enum has exactly two states, success and error, which a big advantage to the usual two optional return values (data and error) which lead to 4 possible states.
enum Result<T> {
case Success(T)
case Error(String, Int)
}
Using the result enum in a completion block finishes the puzzle.
let InvalidURLCode = 999
let NoDataCode = 998
func getFrom(urlString: String, completion:Result<NSData> -> Void) {
// make sure the URL is valid, if not return custom error
guard let url = NSURL(string: urlString) else { return completion(.Error("Invalid URL", InvalidURLCode)) }
let request = NSURLRequest(URL: url)
NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
// if error returned, extract message and code then pass as Result enum
guard error == nil else { return completion(.Error(error!.localizedDescription, error!.code)) }
// if no data is returned, return custom error
guard let data = data else { return completion(.Error("No data returned", NoDataCode)) }
// return success
completion(.Success(data))
}.resume()
}
because the return value is a enum, you should switch off of it.
getFrom("http://www.google.com") { result in
switch result {
case .Success(let data):
// handle successful data response here
let responseString = String(data:data, encoding: NSASCIIStringEncoding)
print("got data: \(responseString)");
case .Error(let msg, let code):
// handle error here
print("Error [\(code)]: \(msg)")
}
}
another solution would be to pass two completion blocks, one for success and one for error. something along the lines of:
func getFrom(urlString: String, successHandler:NSData -> Void, errorHandler:(String, Int) -> Void)
It's very similar to Casey's answer,
but with Swift 5, now we have Result (generic enumeration) implementation in standard library,
//Don't add this code to your project, this has already been implemented
//in standard library.
public enum Result<Success, Failure: Error> {
case success(Success), failure(Failure)
}
It's very easy to use,
URLSession.shared.dataTask(with: url) { (result: Result<(response: URLResponse, data: Data), Error>) in
switch result {
case let .success(success):
handleResponse(success.response, data: success.data)
case let .error(error):
handleError(error)
}
}
https://developer.apple.com/documentation/swift/result
https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md
There's an elegant approach utilising a JavaScript-like Promise library or a Scala-like "Future and Promise" library.
Using Scala-style futures and promises, it may look as follows:
Your original function
func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ())
may be implemented as shown below. It also shows, how to create a promise, return early with a failed future and how to fulfill/reject a promise:
func sendRequest(someData: MyCustomClass) -> Future<NSData> {
guard let url = ... else {
return Future.failure(MySessionError.InvalidURL) // bail out early with a completed future
}
let request = ... // setup request
let promise = Promise<NSData>()
NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard let error = error else {
promise.reject(error) // Client error
}
// The following assertions should be true, unless error != nil
assert(data != nil)
assert(response != nil)
// We expect HTTP protocol:
guard let response = response! as NSHTTPURLResponse else {
promise.reject(MySessionError.ProtocolError) // signal that we expected HTTP.
}
// Check status code:
guard myValidStatusCodeArray.contains(response.statusCode) else {
let message: String? = ... // convert the response data to a string, if any and if possible
promise.reject(MySessionError.InvalidStatusCode(statusCode: response.statusCode, message: message ?? ""))
}
// Check MIME type if given:
if let mimeType = response.MIMEType {
guard myValidMIMETypesArray.contains(mimeType) else {
promise.reject(MySessionError.MIMETypeNotAccepted(mimeType: mimeType))
}
} else {
// If we require a MIMEType - reject the promise.
}
// transform data to some other object if desired, can be done in a later, too.
promise.fulfill(data!)
}.resume()
return promise.future!
}
You might expect a JSON as response - if the request succeeds.
Now, you could use it as follows:
sendRequest(myObject).map { data in
return try NSJSONSerialization.dataWithJSONObject(data, options: [])
}
.map { object in
// the object returned from the step above, unless it failed.
// Now, "process" the object:
...
// You may throw an error if something goes wrong:
if failed {
throw MyError.Failed
}
}
.onFailure { error in
// We reach here IFF an error occurred in any of the
// previous tasks.
// error is of type ErrorType.
print("Error: \(error)")
}