Making a connection using Swift

Previously

Part 1, Working with REST

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

Leave a Comment

Your email address will not be published. Required fields are marked *