REST API calls not working in swift - swift

I'm following this tutorial for making a simple REST API call in swift: https://grokswift.com/simple-rest-with-swift/
The problem I'm running into is that the data task completion handler next gets executed. When I'm debugging it step by step, it just jumps over the completion handler block. Nothing is printed in the console, either.
I've searched for other methods of making REST API calls, but they are all very similar to this one and not working, either.
Here is my code:
let endpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: endpoint) else {
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) -> Void in
guard error == nil else {
print("Error calling GET")
return
}
guard let responseData = data else {
print("Error receiving data")
return
}
do {
print ("Parsing response...")
}
}
task.resume()

Your code looks right to me. I tested it in a Playground and I'm getting the Parsing response... message printed to the console which makes me think the issue is elsewhere in your code or environment. I'd be happy to take a look at the whole project if you can post a Github link or something similar.
Here are the steps I would take to debug an issue like this:
1) Confirm my execution environment has an active internet connection. The Safari app can be used to confirm on iOS devices or the Simulator. Playgrounds can be tested by pasting the following lines.
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
print (try? String(contentsOf: url))
Look for a line in the console output similar to:
Optional("{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}")
2) Confirm the url is valid and returns data by pasting it into a web browser url bar and hitting enter. You will either see JSON printed in the browser or not.
3) Confirm my code is actually getting called when the application runs. You can do this with either breakpoints or print() statements. As OOPer2 pointed out asynchronous callback closures like that used in session.dataTask() execute in a different time than the rest of your code which is why "it just jumps over the completion handler block" while stepping through with the debugger. You'll need to put another breakpoint or print() statement inside the completion handler closure. I'd put the breakpoint on the guard error == nil else { line.
4) Make sure the application is still executing when the network request finishes and the completion handler closure executes. If your code is in a ViewController running in an iOS application it's probably fine, but if it's running in a Playground it may not be. Playgrounds by default stop execution once the last line of code has been evaluated which means the completion closure will never execute. You can tell a Playground to continue executing indefinitely by importing the PlaygroundSupport framework and setting needsIndefiniteExecution = true on the current Playground page. Paste the entire code block below into a Playground to see it in action:
import Foundation
import PlaygroundSupport
// Keep executing the program after the last line has evaluated so the
// closure can execute when the asynchronous network request finishes.
PlaygroundPage.current.needsIndefiniteExecution = true
// Generic Result enum useful for returning values OR an error from
// asynchronous functions.
enum Result<T> {
case failure(Error)
case success(T)
}
// Custom Errors to be returned when something goes wrong.
enum NetworkError: Error {
case couldNotCreateURL(for: String)
case didNotReceiveData
}
// Perform network request asynchronous returning the result via a
// completion closure called on the main thread.
//
// In really life the result type will not be a String, it will
// probably be an array of custom structs or similar.
func performNetworkRequest(completion: #escaping (Result<String>)->Void ) {
let endpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: endpoint) else {
let error = NetworkError.couldNotCreateURL(for: endpoint)
completion(Result.failure(error))
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) -> Void in
// This closure is still executing on a background thread so
// don't touch anything related to the UI.
//
// Remember to dispatch back to the main thread when calling
// the completion closure.
guard error == nil else {
// Call the completion handler on the main thread.
DispatchQueue.main.async {
completion(Result.failure(error!))
}
return
}
guard let responseData = data else {
// Call the completion handler on the main thread.
DispatchQueue.main.async {
completion(Result.failure(NetworkError.didNotReceiveData))
}
return
}
// Parse response here...
// Call the completion handler on the main thread.
DispatchQueue.main.async {
completion(Result.success("Sucessfully parsed results"))
}
}
task.resume()
}
performNetworkRequest(completion: { result in
// The generic Result type makes handling the success and error
// cases really nice by just using a switch statement.
switch result {
case .failure(let error):
print(error)
case .success(let parsedResponse):
print(parsedResponse)
}
})

Why you dont use this Library Alamofire is an HTTP networking library written in Swift.
Add this line to your Podfile
pod 'Alamofire', '~> 4.4'
Then, run the following command:
pod install
Then in your ViewController file:
import Alamofire
Alamofire.request("https://jsonplaceholder.typicode.com/todos/1").responseJSON { response in
print("Request: \(String(describing: response.request))") // original url request
print("Response: \(String(describing: response.response))") // http url response
print("Result: \(response.result)") // response serialization result
if let json = response.result.value {
print("JSON: \(json)") // serialized json response
}
If let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)") // original server data as UTF8 string
}
}
And in here are an example of how to parse the result.
https://github.com/CristianCardosoA/JSONParser
For more info about Alamofire:
https://github.com/Alamofire/Alamofire
I hope this help.

Related

URLSession.shared.dataTask Code Block Not Running

I'm trying to make a fairly simple API call in Swift but, for some reason, my dataTask code is not running. I've made sure that the .resume() is there. This code has worked in the past but, something has changed recently and I don't know what it is. The only thing I can think of is the url. I've changed the ingredients but, when putting the url into a browser, it returns JSON data normally. When running this function, I get two "Outside URLSession.shared.dataTask....." messages in a row with nothing in between, indicating that the URLSession block of code isn't running. I'm a little new to APIs so, any help would be greatly appreciated. Please let me know if there's any more information I can provide. Also, I'm on an older MacBook and am using Swift5 if that makes a difference. Thanks!
let url: URL! = URL(string: "https://api.spoonacular.com/recipes/findByIngredients?ingredients=" + ingredientString + "&apiKey=aaabbbccc111222333")
print("URL: " + url.absoluteString)
let request = URLRequest(url: url)
// Make the API call
print("Outide URLSession.shared.dataTask.....")
let session = URLSession.shared.dataTask(with: request) { data, response, error in
print("Inside URLSession.shared.dataTask.....")
DispatchQueue.main.async {
print("Inside DispatchQueue.main.async....")
if data == nil {
print("No data recieved.")
}
print("data != nil.... Moving on to JSONDecoder....")
self.model = try! JSONDecoder().decode([RecipeSearchElement].self, from: data!)
}
}
session.resume()
print("Outside URLSession.shared.dataTask.....")
Unrelated to your immediate question at hand (which I answered elsewhere), I would advise a few changes to the routine:
One should not build a URL through string interpolation. Use URLComponents. If, for example, the query parameter included a space or other character not permitted in a URL, URLComponents will percent-encode it for you. If do not percent-encode it properly, the building of the URL will fail.
I would avoid try!, which will crash the app if the server response was not what you expected. One should use try within a do-catch block, so it handles errors gracefully and will tell you what is wrong if it failed.
I would recommend renaming the URLSessionDataTask to be task, or something like that, to avoid conflating “sessions” with the “tasks” running on that session.
I would not advise updating the model from the background queue of the URLSession. Fetch and parse the response in the background queue and update the model on the main queue.
Thus:
var components = URLComponents(string: "https://api.spoonacular.com/recipes/findByIngredients")
components?.queryItems = [
URLQueryItem(name: "ingredients", value: ingredientString),
URLQueryItem(name: "apiKey", value: "aaabbbccc111222333")
]
guard let url = components?.url else {
print("Unable to build URL")
return
}
// Make the API call
let task = URLSession.shared.dataTask(with: url) { data, _, error in
DispatchQueue.main.async {
guard error == nil, let data = data else {
print("No data received:", error ?? URLError(.badServerResponse))
return
}
do {
let model = try JSONDecoder().decode([RecipeSearchElement].self, from: data)
DispatchQueue.main.async {
self.model = model
}
} catch let parseError {
print("Parsing error:", parseError, String(describing: String(data: data, encoding: .utf8)))
}
}
}
task.resume()
In a more advanced observation, I would never have a network call update the model directly. I would leave that to the caller. For example, you could use a completion handler pattern:
#discardableResult
func fetchIngredients(
_ ingredientString: String,
completion: #escaping (Result<[RecipeSearchElement], Error>) -> Void
) -> URLSessionTask? {
var components = URLComponents(string: "https://api.spoonacular.com/recipes/findByIngredients")
components?.queryItems = [
URLQueryItem(name: "ingredients", value: ingredientString),
URLQueryItem(name: "apiKey", value: "aaabbbccc111222333")
]
guard let url = components?.url else {
completion(.failure(URLError(.badURL)))
return nil
}
// Make the API call
let task = URLSession.shared.dataTask(with: url) { data, _, error in
print("Inside URLSession.shared.dataTask.....")
DispatchQueue.main.async {
guard error == nil, let data = data else {
DispatchQueue.main.async {
completion(.failure(error ?? URLError(.badServerResponse)))
}
return
}
do {
let model = try JSONDecoder().decode([RecipeSearchElement].self, from: data)
DispatchQueue.main.async {
completion(.success(model))
}
} catch let parseError {
DispatchQueue.main.async {
completion(.failure(parseError))
}
}
}
}
task.resume()
return task
}
And then the caller could do:
fetchIngredients(ingredientString) { [weak self] result in
switch result {
case .failure(let error): print(error)
case .success(let elements): self?.model = elements
}
}
This has two benefits:
The caller now knows when the model is updated, so you can update your UI at the appropriate point in time (if you want).
It maintains a better separation of responsibilities, architecturally avoiding the tight coupling of the network layer with that of the view or view model (or presenter or controller) layers.
Note, I am also returning the URLSessionTask object in case the caller would like to cancel it at a later time, but I made it an #discardableResult so that you do not have to worry about that if you are not tackling cancelation at this point.
If you (a) are reaching the “outside” message, but not seeing the “inside” message; and (b) are absolutely positive that you are reaching the resume statement, it is one of a few possibilities:
The app may be terminating before the asynchronous request has time to finish. This can happen, for example, if this is a command-line app and you are allowing the app to quit before the asynchronous request has a chance to finish. If you want a command-line app to wait for a network request to finish, you might run a RunLoop that does not exit until the network request is done.
It can also happen if you use a playground and neglect to set needsIndefiniteExecution:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
For the sake of completeness, there are a few other, less common, possibilities:
You have some other network request whose completion handler is blocked/deadlocked, thereby preventing anything else from running on the URLSession dedicated, serial, queue.
You have thread explosion somewhere else in your code, exhausting the limited pool of worker threads, preventing other tasks/operations from being able to get an available worker thread.

No response when doing a GET-Request with Alamofire [duplicate]

In a swift 2 command line tool (main.swift), I have the following:
import Foundation
print("yay")
var request = HTTPTask()
request.GET("http://www.stackoverflow.com", parameters: nil, completionHandler: {(response: HTTPResponse) in
if let err = response.error {
print("error: \(err.localizedDescription)")
return //also notify app of failure as needed
}
if let data = response.responseObject as? NSData {
let str = NSString(data: data, encoding: NSUTF8StringEncoding)
print("response: \(str)") //prints the HTML of the page
}
})
The console shows 'yay' and then exits (Program ended with exit code: 0), seemingly without ever waiting for the request to complete. How would I prevent this from happening?
The code is using swiftHTTP
I think I might need an NSRunLoop but there is no swift example
Adding RunLoop.main.run() to the end of the file is one option. More info on another approach using a semaphore here
I realize this is an old question, but here is the solution I ended on. Using DispatchGroup.
let dispatchGroup = DispatchGroup()
for someItem in items {
dispatchGroup.enter()
doSomeAsyncWork(item: someItem) {
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
exit(EXIT_SUCCESS)
}
dispatchMain()
You can call dispatchMain() at the end of main. That runs the GCD main queue dispatcher and never returns so it will prevent the main thread from exiting. Then you just need to explicitly call exit() to exit the application when you are ready (otherwise the command line app will hang).
import Foundation
let url = URL(string:"http://www.stackoverflow.com")!
let dataTask = URLSession.shared.dataTask(with:url) { (data, response, error) in
// handle the network response
print("data=\(data)")
print("response=\(response)")
print("error=\(error)")
// explicitly exit the program after response is handled
exit(EXIT_SUCCESS)
}
dataTask.resume()
// Run GCD main dispatcher, this function never returns, call exit() elsewhere to quit the program or it will hang
dispatchMain()
Don't depend on timing.. You should try this
let sema = DispatchSemaphore(value: 0)
let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
print("after image is downloaded")
// signals the process to continue
sema.signal()
}
task.resume()
// sets the process to wait
sema.wait()
If your need isn't something that requires "production level" code but some quick experiment or a tryout of a piece of code, you can do it like this :
SWIFT 3
//put at the end of your main file
RunLoop.main.run(until: Date(timeIntervalSinceNow: 15)) //will run your app for 15 seconds only
More info : https://stackoverflow.com/a/40870157/469614
Please note that you shouldn't rely on fixed execution time in your architecture.
Swift 4: RunLoop.main.run()
At the end of your file
// Step 1: Add isDone global flag
var isDone = false
// Step 2: Set isDone to true in callback
request.GET(...) {
...
isDone = true
}
// Step 3: Add waiting block at the end of code
while(!isDone) {
// run your code for 0.1 second
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1))
}

Swift Updating variable inside of data task

I am writing a Network class for my app. One function carries out the actual request to the API I am using
as part of this function I am using a URLSession.shard.dataTask().
var decodedResponse = SongLinkAPIResponse()
let task = session.dataTask(with: url) { data, response, error in
let result = data.map(Result.success) ?? Result.failure(DataLoaderError.network(error!))
let handlerResult = handler(result)
// This handler just decodes the downloaded JSON into a SongLinkAPIResponse struct.
// This works fine and I can see this is working
DispatchQueue.main.async {
switch handlerResult {
case .success(let response):
// When I run this code in a playground I can see that this line shows as working in the sidebar
decodedResponse = response
case .failure(let error):
print(error)
}
}
}
task.resume()
//However when returning this variable it just has the empty default configuration of the struct
return decodedResponse
Any ideas on why the variable is not updating?
Thanks in advance
session.dataTask is asynchronous. It will execute some time in the future.
Your code will execute in the following order.
// 1
var decodedResponse = SongLinkAPIResponse()
// 2
let task = session.dataTask(with: url) { data, response, error in
// 4
...
}
task.resume()
// 3
return decodedResponse
Here are some possible explanations/solutions:
Function that returns asynchronously retrieved value
Returning data from async call in Swift function
Swift write an async/await method with return value

Swift program never enters CompletionHandler for a dataTask

I am in the process of implementing a REST API with Swift. Of course, part of this API is using HTTP requests to retrieve and send data.
Full disclosure, I am inexperienced with Swift and am using this as a learning project to get my feet wet, so to speak. But it's turned into much more of a difficult project than I anticipated.
In implementing the first get method, I have (finally) gotten rid of all the compilation errors. However, when I call the function which utilizes the URLRequest, URLSession, dataTask, etc, it is never entered.
Upon debugging the program, I can watch the program execution reach the CompletionHandler, and skip over it right to "task.resume()."
A similar construction works in a Swift Playground, but does not work in the actual project proper.
So far I have tried a few things, namely making the function access a class instance variable, in hopes that that would force it to execute. But it does not.
I think the issue may be dealing with synchronicity, and perhaps I need to use a Semaphore, but I want to make sure I'm not missing anything obvious first.
import Foundation
/**
A class to wrap all GET and POST requests, to avoid the necessity of repeatedly writing request code in each API method.
*/
class BasicRequest {
private var url: URL
private var header: [String: String]
private var responseType: String
private var jsonResponse: Any?
init(url: URL, header: [String: String], responseType: String) {
self.url = url
self.header = header
self.responseType = responseType
} //END INIT
public func requestJSON() -> Any {
// Create the URLRequest object, and fill the header with the header fields as provided.
var urlRequest = URLRequest(url: self.url)
for (value, key) in self.header {
urlRequest.addValue(value, forHTTPHeaderField: key)
}
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
print("Entered the completion handler")
if error != nil {
return
}
guard let httpResponse = response as? HTTPURLResponse, 200 == httpResponse.statusCode else {
print("HTTP Request unsuccessful")
return
}
guard let mime = response?.mimeType, mime == "application/json" else {
print("Not a JSON response")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print(json)
self.jsonResponse = json
} catch {
print("Could not transform to JSON")
return
}
}
task.resume()
return "Function has returned"
} //END REQUESTJSON
}
The expected result would be returning a JSON object, however that does not seem to be the case.
With respect to error messages, I get none. The only log I get in the debugger is the boilerplate "process exited with code 0."
To be truthful, I'm at a loss with what is causing this not to work.
It appears you're writing this in a command-line app. In that case the program is terminating before the URLRequest completes.
I think the issue may be dealing with synchronicity, and perhaps I need to use a Semaphore, but I want to make sure I'm not missing anything obvious first.
Exactly.
The typical tool in Swift is DispatchGroup, which is just a higher-level kind of semaphore. Call dispatchGroup.enter() before starting the request, and all dispatchGroup.leave() at the end of the completion handler. In your calling code, include dispatchGroup.wait() to wait for it. (If that's not clear, I can add code for it, but there are also a lot of SO answers you can find that will demonstrate it.)

Unit test with Swift: body of closure not executed

There are functions that send a request to server, get response and print result. They always work in the iOS app itself but only sometimes (looks like randomly) in unit-tests of this app.
Main issue: Xcode doesn't enter the body of a closure in unit-tests, just skips it.
Any ideas how can it be fixed? Image of the problem in Xcode.
The most likely reason because the completion closure of your requests are not being exectued is that they are performing an asynchronous operation, while the tests run synchronously. This means that the test finishes running while your network request is still processing.
Try using XCTestExpectation:
func testIt() {
let expectation = expectationWithDescription("foobar")
// request setup code here...
Alamofire.request(.POST, "...")
.responseJSON { response in
//
// Insert the test assertions here, for example:
//
if let JSON = response.result.value as? [String: AnyObject] {
XCTAssertEqual(JSON["id"], "1")
} else {
XCTFail("Unexpected response")
}
//
// Remember to call this at the end of the closure
//
expectation.fulfill()
}
//
// This will make XCTest wait for up to 10 seconds,
// giving your request expectation time to fulfill
//
waitForExpectationsWithTimeout(10) { error
if let error = error {
XCTFail("Error: \(error.localizedDescription)")
}
}
}