September 23, 2016 - Swift 3.0
Now that Xcode 8 is really here, it’s about time we thought about updating some of our code to Swift 3.0. If you’ve started migrating to Swift 3.0 you know it can be a real pain. I’ve got plans to update a bunch of posts but let’s start with the simplest stuff: Making networking calls using NSURLSession
and parsing the resulting JSON.
The first thing you need to know is that it’s not called NSURLSession
anymore. In an effort to streamline our code (and make everything harder to google), tons of NS
prefixes have been removed in Swift 3.0.
This tutorial takes the Swift 2.2 code and shows how to update it to Swift 3.0 using Xcode 8.0.
GET Request
Here’s a simple Swift 2.2 example of making a GET request:
// Set up the URL request
let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = NSURL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
// set up the session
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
// make the request
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
// do stuff with response, data & error here
print(error)
print(response)
})
task.resume()
If we try to run that code in Xcode 8 we’ll get a handful of errors:
We could try letting Xcode correct those errors automatically using its suggestions. But sometimes it’s better to do things manually so we really understand what we’re changing. So let’s look at the official Swift 3.0 migration guide. It’d be a good idea to at least skim through that whole page now.
The part we need to deal with first is under SDK:
The ‘NS’ prefix from key Foundation types is getting removed in Swift 3, see SE-0086 - Drop NS Prefix in Swift Foundation.
Following that GitHub link sends us to a long list of places where the NS
prefix has been removed. It also links to the new Swift Core Libraries documentation which includes new Swiftier versions of lots of classes like NSURL
.
So our task is two-fold:
- Check if there’s a new Swift Core class and, if so, switch to it
- Otherwise check for any
NS
prefixes that need to be removed.
Fortunately, those two tasks pretty much come down to the same thing: delete NS
at the start of any class names and see if the code compiles :)
So let’s try that with the GET request code (one chunk at a time). First we’ll remove all of the NS
prefixes:
// Set up the URL request
let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(URL: url)
// set up the session
let config = URLSessionConfiguration.defaultSessionConfiguration()
let session = URLSession(configuration: config)
// make the request
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
// do stuff with response, data & error here
print(error)
print(response)
})
task.resume()
And now we have more errors. This happens a lot when migrating to Swift 3.0. Fortunately most of them aren’t too hard to figure out:
For the first error, Xcode is telling us exactly how to fix it: change URL:
to url:
let urlRequest = URLRequest(url: url)
The second error seems more cryptic. It’s telling us that URLSessionConfiguration.defaultSessionConfiguration()
doesn’t exist. Let’s try retyping the line to see what autocomplete suggests. When we get to URLSessionConfiguration.
the autocomplete list includes default
which looks like what we want:
let config = URLSessionConfiguration.default
Alternatively you could just use the shared session without setting up a URLSessionConfiguration
:
let session = URLSession.shared
You should be noticing now that Swift 3.0 is a lot less wordy than previous versions. Often the fixes we need to make are shortening super verbose Objective-C style syntax.
That fixes those 2 errors but I’m getting another one now:
'dataTaskWithRequest(_:completionHandler:)' has been renamed to 'dataTask(with:completionHandler:)'
That’s another example of the less verbose Swift and the compiler being helpful. Let’s update that too:
let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// ...
})
task.resume()
Save & run to make sure that works for you. It should print out the response that looks something like:
nil
Optional( { URL: https://jsonplaceholder.typicode.com/todos/1 } { status code: 200, headers {
...
And that’s all it takes to update this networking call to Swift 3.0:
- Delete all of the NS-prefixes
- See what the compiler complains about
- Make the compiler happy (mostly deleting verbosity, relying on the docs and autocomplete when you need to)
Other conversions to Swift 3.0 can be a lot more complicated. We’ll get to some of those in future posts. It can be overwhelming when you’re converting a whole project, especially when every fix seems to pop up more errors. Focus on the simple fixes first. The more complex issues will be more obvious when they’re they only errors left.
Parsing JSON
I don’t often perform a networking call without parsing the resulting JSON. Here’s what the old code using NSJSONSerialization
looked like:
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let todo = try NSJSONSerialization.JSONObjectWithData(responseData, options: []) as? [String: AnyObject] else {
print("error trying to convert data to JSON")
return
}
// now we have the todo, let's just print it to prove we can access it
print("The todo is: " + todo.description)
// the todo object is a dictionary
// so we just access the title using the "title" key
// so check for a title and print it if we have one
guard let todoTitle = todo["title"] as? String else {
print("Could not get todo title from JSON")
return
}
print("The title is: " + todoTitle)
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
To migrate to Swift 3.0 let’s do the same as above: remove the NS
prefixes and see what happens (and we’ll update the dataTask
function that we already figured out).
The only NS
prefix is on NSJSONSerialization
:
do {
guard let todo = try JSONSerialization.JSONObjectWithData(responseData, options: []) as? [String: AnyObject] else {
print("error trying to convert data to JSON")
return
}
// ...
} catch {
print("error trying to convert data to JSON")
return
}
And like before that triggers another error that’s just a renaming:
'JSONObjectWithData(_:options:)' has been renamed to 'jsonObject(with:options:)'
So we’ll update that call:
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: AnyObject] else {
print("error trying to convert data to JSON")
return
}
// ...
} catch {
print("error trying to convert data to JSON")
return
}
That should compile & run, printing out the description and title of the first todo:
The todo is: ["title": delectus aut autem, "userId": 1, "id": 1, "completed": 0]
The title is: delectus aut autem
And That’s All
That’s how you update an existing NSURLSession
GET networking call (with NSJSONSerialization
JSON parsing) to Swift 3.0. Over the next while I’ll be working through how to update more networking code to Swift 3.0, using URLSession
and Alamofire. If there’s something specific you want to see, leave a comment or email me.
Here’s the final code: GitHub gist: URLSession Get Call in Swift 3.0
If you’d like more Swift tutorials on topics like this one, sign up below to get them sent directly to your inbox.