Previously
In the previous article, we installed a local JSON server, to make testing the app we are about to create in this article, fast.
In this article, we are going to create a console app, using Swift. In the following article, we will be doing the same process, using C++.
Creating a console app in Xcode
Launch Xcode, then create a new project using
- Select “Create a new Xcode project” from the “Welcome to Xcode” window, OR
- File -> New -> Project menu item, OR
- ⌘ + Shift + N
Select “Command Line Tool”
Enter in the name for your app. You can leave the organization name to the defaults.
We are presented with a basic console app project; the file that you are interested in is main.swift.
With a Swift console app, you don’t need to define a main function, like you would with other languages. Instead, you can just start typing code and the app will execute the commands in the order that they appear.
So, let’s write up some code.
To connect to the local JSON server, we are going to:
- Create a URL, initialized with a string
- Initialize a URLRequest with our URL and set the options
- Use the shared URLSession to create a dataTask
- Create a closure for our dataTask to handle the HTTP response
A little explanation of the classes we are using…
The URL represents the location of an item, which in this case is a server, and has many functions for handling the path it contains.
The URLRequest handles the load request to our server.
The URLSession handles the data transfer from our server.
So, here is what our initial code looks like:
import Foundation let serverURL = "http://localhost:3000" // Function that will attempt to connect to a REST endpoint func connectToServer() { // Create a URL from our string path guard let urlPath = URL(string: serverURL) else { print("Failure to create a URL path"); return } // Set up the request object var request = URLRequest(url: urlPath) request.httpMethod = "GET" // Create a data task that will be processed asynchronously let session = URLSession.shared let task = session.dataTask(with: request) } connectToServer()
This code, if you server is running, will connect to our server, but, we will also want to get some data.
We have to let the task know that we want it to do it’s job and put in some code to handle the response from the server.
We will implement that using closures.
Data task
A Swift closure is the equivalent of a lambda, in C++, a function that is created on the spot and not declared elsewhere.
There are plenty of times where you need code to handle something, like a server response, but the code makes more sense to be close to the originating code and not elsewhere.
You can create another function and pass that to our function as a parameter, but in this example, I’m just going to use a closure.
The dataTask completionHandler is documented at the Apple developer docs if you want more details.
Here is the code I created for handling the server response:
import Foundation let serverURL = "http://localhost:3000" // Function that will attempt to connect to a REST endpoint func connectToServer() { // Create a URL from our string path guard let urlPath = URL(string: serverURL) else { print("Failure to create a URL path"); return } // Set up the request object var request = URLRequest(url: urlPath) request.httpMethod = "GET" // Create a data task that will be processed asynchronously let session = URLSession.shared let task = session.dataTask(with: request, completionHandler: { (data, response, error) in // If we have an error, well, that's a problem guard error == nil else { print("Detected error"); return } // If we can't get the data, that's a problem too guard let received = data else { print("Unable to get data"); return } // And, if we can't get the response, we shouldn't proceed guard let httpResponse = response as? HTTPURLResponse else { print("Could not get theHTTP response"); return } // If we made it to this point, then we are golden print(httpResponse) print(received.description) }) // This is the line that tells the task to start it's job task.resume() } connectToServer()
If you were to run the program right now, you notice that nothing happens.
What gives?
Well, the task is running asynchronously, in the background, and since the code was proceeding along, without any clue that there was a task running in the background, the app just exits.
We are going to create a loop… a nasty one… that will keep the program running, while the dataTask runs.
To make the loop stop, we have to have a way of knowing that the task is complete.
So, we will send a completion function, that the dataTask will call, to indicate that the app can quit.
import Foundation let serverURL = "http://localhost:3000" var processIsDone = false // Function that will attempt to connect to a REST endpoint func connectToServer(callWhenDone: @escaping () -> Void) { // Create a URL from our string path guard let urlPath = URL(string: serverURL) else { print("Failure to create a URL path"); return } // Set up the request object var request = URLRequest(url: urlPath) request.httpMethod = "GET" // Create a data task that will be processed asynchronously let session = URLSession.shared let task = session.dataTask(with: request, completionHandler: { data, response, error in // If we have an error, well, that's a problem guard error == nil else { print("Detected error"); return } // If we can't get the data, that's a problem too guard let received = data else { print("Unable to get data"); return } // And, if we can't get the response, we shouldn't proceed guard let httpResponse = response as? HTTPURLResponse else { print("Could not get theHTTP response") return } // If we made it to this point, then we are golden print(httpResponse) print(received.description) callWhenDone() }) // This is the line that tells the task to start it's job task.resume() } connectToServer(callWhenDone: { () in // The closure that will tell the app that // it is ready to move on from the loop print("Task is done; connectToServer called") processIsDone = true }) var x = 0 // The nasty loop, that continues while the bool is true while processIsDone == false { // We have to do something, right? x += 1 } print("Application will exit now")
The Swift code connects to our local JSON server and retrieves the response, printed to the Console in Xcode.
Failures
One thing to add, is, if there is an error, the code should have a way of handling that too.
Let’s add a parameter to the connectToServer function, to call out to when a problem was detected.
To make the parameters in the function look neat, we can use Typealiases.
import Foundation // The address to our server let serverURL = "http://localhost:3000" // The variable that lets the loop know it is time to stop var processIsDone = false // Typealiases to make our function parameters look neater typealias Success = () -> Void typealias Failure = (_ error: Error?) -> Void // Function that will attempt to connect to a REST endpoint func connectToServer(callWhenDone success: @escaping Success, callWhenError failure: Failure?) { // Create a URL from our string path guard let urlPath = URL(string: serverURL) else { print("Failure to create a URL path"); return } // Set up the request object var request = URLRequest(url: urlPath) request.httpMethod = "GET" // Create a data task that will be processed asynchronously let session = URLSession.shared let task = session.dataTask(with: request, completionHandler: { (data, response, error) in // A closure to handle the task process // If we have an error, well, that's a problem guard error == nil else { failure?(error); return } // If we can't get the data, that's a problem too guard let received = data else { failure?(error); return } // And, if we can't get the response, we shouldn't proceed guard let httpResponse = response as? HTTPURLResponse else { print("Could not get theHTTP response"); return } // If we made it to this point, then we are golden print(httpResponse) print(received.description) success() }) // This is the line that tells the task to start it's job task.resume() } // Call the function, passing in a success and failure closure connectToServer(callWhenDone: { () in // The closure that will tell the app that // it is ready to move on from the loop print("Task is done; connectToServer called") processIsDone = true }, callWhenError: { ( error ) in // A closure to handle when an error was detected if let err = error { print("Detected error: \(err.localizedDescription)") } processIsDone = true }) // Loop to give the asynchronous data task enough time to get // a response from the server var x = 0 while processIsDone == false { // We have to do something, right? x += 1 } print("Application will exit now")
Now, the function will exit the dataTask gracefully when there is an error and the application won’t get stuck on the forever running loop.
In the next article, we will be making the same type of connection, using C++.
Index
Article Series Index