Using Swift to talk to a Web Server (Edison)

Hi, all!


I just followed a (rather poor) tutorial by Intel that has some (rather poor) source code for the project.


Intel builds a small app with some sliders that send their values to an internet server, (The Intel Edison attached to some motors).


On the swift side, the only thing I'm confused about how the sliders work. Here is the code:


    @IBAction func leftSliderMoved(sender: UISlider) {
        //Updatng the UILabel for the slider
        let currentVal = Int(sender.value);
        leftSliderLabel.text = "\(currentVal)";

        //The actual internet stuff
        let url = NSURL(string: "http://\(ipField.text):8080/change?left=\(leftMotorSlider.value)");
        let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {
            (data, response, error) in
            print(NSString(data: data!, encoding: NSUTF8StringEncoding));
        }

        task.resume();
    }


The textBox ipField contains the text for the local ip address on the wifi network for the web server. We use this text to go to a url at port 8080. A few questions:


  1. What am I doing in the rest of the url? It looks like I'm sending an order to change the left motor to a specific value. What's up with the syntax?
  2. Notice on line 10 that data! is a force-unwrapped optional. This does no good. If the wrong ip address is in the box or a connection fails, the app crashes. Is there a way to safely unwrap dataand display a 'connection failed' message instead of crashing the entire app?
  3. Lastly, if someone could explain to me what's happening in let task = and downwards, I would really appreciate it. The tutorial explains nothing.


Feel free to explain things or just drop a helpful link if you're short on time. I'm really interested in this stuff and would love to learn more.


Thanks so much!

Answered by DTS Engineer in 125832022

1. What am I doing in the rest of the url?

This is forming a URL via string interpolation. The stuff inside

\()
is rendered to a string and then is inserted into the top-level string. For example, if
ipField.text
was
1.2.3.4
and
leftMotorSlider.value
was
5
, this would yield the URL
http://1.2.3.4:8080/change?left=5
.

Building URLs by manipulating strings is fine for a simple test project but for real code I strongly recommend NSURLComponents; it handles a world of special cases that are a pain to handle yourself. Here’s how I do this:

let c = NSURLComponents(string: "http://xxx:8080/change")!
c.host = "1.2.3.4"
c.queryItems = [NSURLQueryItem(name: "left", value: "5")]
print(c.URL!)  // prints "http://1.2.3.4:8080/change?left=5"

For example, NSURLComponents will do the right thing if the

host
property is set to an IPv6 address.

2. Notice on line 10 that data! is a force-unwrapped optional. This does no good.

Quite.

You should check the

error
value to detect network transport errors. You should check the HTTP status code in
response
to detect server-side errors. Here’s how I usually do this:
….dataTaskWithURL(…) { (data, response, error) in
    guard let error = error else {
        // … handle transport error …
        return
    }
    let response = response as! NSHTTPURLResponse
    guard (response.statusCode / 100) == 2 else {
        // … handle server error …
        return
    }
    // … success …
}

Some things to note here:

  • I force cast (

    as!
    )
    response
    to NSHTTPURLResponse because NSURLSession should always give you an NSHTTPURLResponse in response to an
    http:
    or
    https:
    request.
  • HTTP status codes in the 200 range are usually associated with successful completion. You may need to adjust that check depending on how your server responds to errors.

3. Lastly, if someone could explain to me what's happening in let task = and downwards, I would really appreciate it.

The

let
itself (line 8) creates a task.

You pass a closure (lines 9 and 10) when you create the task and that closure is run by the session when the task completes.

The call to

resume()
starts the task. By default tasks are started in the suspended state so that, once you’ve created the task, you can use it to set up the bookkeeping necessary to handle the task completing. In this case there’s no such bookkeeping, so you immediately resume the task.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

So I see that this is part of the NSURL class and that these 'sessions' can be created in order to have easier customization. However, I have yet to find an example with syntax that's even close to this. Might there be a more standardized way to rewrite this code?

Accepted Answer

1. What am I doing in the rest of the url?

This is forming a URL via string interpolation. The stuff inside

\()
is rendered to a string and then is inserted into the top-level string. For example, if
ipField.text
was
1.2.3.4
and
leftMotorSlider.value
was
5
, this would yield the URL
http://1.2.3.4:8080/change?left=5
.

Building URLs by manipulating strings is fine for a simple test project but for real code I strongly recommend NSURLComponents; it handles a world of special cases that are a pain to handle yourself. Here’s how I do this:

let c = NSURLComponents(string: "http://xxx:8080/change")!
c.host = "1.2.3.4"
c.queryItems = [NSURLQueryItem(name: "left", value: "5")]
print(c.URL!)  // prints "http://1.2.3.4:8080/change?left=5"

For example, NSURLComponents will do the right thing if the

host
property is set to an IPv6 address.

2. Notice on line 10 that data! is a force-unwrapped optional. This does no good.

Quite.

You should check the

error
value to detect network transport errors. You should check the HTTP status code in
response
to detect server-side errors. Here’s how I usually do this:
….dataTaskWithURL(…) { (data, response, error) in
    guard let error = error else {
        // … handle transport error …
        return
    }
    let response = response as! NSHTTPURLResponse
    guard (response.statusCode / 100) == 2 else {
        // … handle server error …
        return
    }
    // … success …
}

Some things to note here:

  • I force cast (

    as!
    )
    response
    to NSHTTPURLResponse because NSURLSession should always give you an NSHTTPURLResponse in response to an
    http:
    or
    https:
    request.
  • HTTP status codes in the 200 range are usually associated with successful completion. You may need to adjust that check depending on how your server responds to errors.

3. Lastly, if someone could explain to me what's happening in let task = and downwards, I would really appreciate it.

The

let
itself (line 8) creates a task.

You pass a closure (lines 9 and 10) when you create the task and that closure is run by the session when the task completes.

The call to

resume()
starts the task. By default tasks are started in the suspended state so that, once you’ve created the task, you can use it to set up the bookkeeping necessary to handle the task completing. In this case there’s no such bookkeeping, so you immediately resume the task.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you so much for your time and support here. This makes a lot of sense to me. I'm getting an error when I try to print the URL. It appears that assigning the host value just returns a nil, as if the passed string is invalid. This is odd to me. Will update if I figure it out. Thanks again!

Using Swift to talk to a Web Server (Edison)
 
 
Q