Updating to Swift 2.0 and iOS 9
September 10, 2015

Yesterday Apple released the Xcode 7 GM and iOS 9 is coming next week. We can finally use Swift 2.0 without worrying about the language constantly changing :) So let’s figure out how to update our most recent tutorial to Swift 2.0 and iOS 9.

This tutorial was originally written using Swift 2.0, Xcode 7.0, Alamofire 2.0.0, and SwiftyJSON 2.3.0. Since then Alamofire 3.0.0 has been released with improvements to response serialization. Instructions to update the code for Alamofire 3.0.0 have been added at the bottom.

To start we’ll grab the code from GitHub:


git clone git@github.com:cmoulton/grokRESTPageViewController.git
cd grokRESTPageViewController
git checkout swift-1.2

The git checkout swift-1.2 bit is needed since the code from this tutorial has been merged into the master branch now that Xcode 7 has been released.`

And we’ll create a new branch so we aren’t stuck if we need to go back to Swift 1.2:


git checkout -b swift-2.0

Normally we would run pod install after git clone but if we do we’ll find out that SwiftyJSON isn’t compatible with Swift 2.0 yet. (Believe me, I checked, the compiler errors are pretty ugly.) Fortunately SwiftyJSON does have a branch in their git repo that supports Swift 2.0. We just need to update our Podfile to use it.

To specify the branch for a pod we have to add 2 properties: :git with the location of the pod and :branch with the name of the branch. We can’t include a version number for the pod if we’re using :branch, it’ll just use the latest code on that branch.

We’ll also need to update Alamofire to version 2.0.0 and SwiftyJSON to version 2.3.0 to get Swift 2.0 support.

So change the Podfile from:


source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
pod 'Alamofire', '~> 1.3.0'
pod 'SwiftyJSON', '~> 2.2.0'

to:


source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
pod 'Alamofire', '~> 2.0.0'
pod 'SwiftyJSON', '~> 2.3.0'

And run pod install.

Auto-Updated the Swift Syntax

Open the .xcworkspace file in Xcode 7. You’ll be presented with a dialog asking if you want Xcode to convert your project to the latest Swift syntax. Click on “Convert”:

Swift syntax conversion dialog from Xcode

Click through the dialogs until you get to one that asks which targets it should convert. Un-check the pods targets since they’re already Swift 2.0 compatible:

Selecting targets for syntax updating

Click “Next” and Xcode will start generating a preview of the converted code:

Syntax updating preview

Some projects will have lots of changes but this one only has a few.

And click “Save” to accept Xcode’s changes. If you get a warning about uncommitted changes just accept it.

Dealing with Errors

Now try to build your project. You’ll get a bunch of warnings and errors, mostly related to Alamofire:

Syntax updating preview

Click on the first warning (“Update to recommended settings”) and accept the change:

Syntax updating preview

The only other item that looks like it’s not related to Alamofire is println has been renamed to print. That’s an easy change so fix it:


required init?(json: SwiftyJSON.JSON) {
  print(json)
  self.symbol = json["symbol"].string
  self.ask = json["Ask"].string
  self.yearHigh = json["YearHigh"].string
  self.yearLow = json["YearLow"].string
}

Handling Throws

The first error we have is from this bit of code:


var jsonError: NSError?
let jsonData: AnyObject? = NSJSONSerialization.JSONObjectWithData(responseData, options: nil, error: &jsonError)

If we look at the docs for NSJSONSerialization.JSONObjectWithData we can see that the signature for that function has changed to:


public class func JSONObjectWithData(data: NSData, options opt: NSJSONReadingOptions) throws -> AnyObject

Instead of error: &jsonError we have throws after the signature. That means that this function might throw an error. To handle that case we’ll use do-try-catch:


let jsonData: AnyObject?
do {
  jsonData = try NSJSONSerialization.JSONObjectWithData(responseData, options: [])
} catch  {
  // TODO: handle
}

We deleted var jsonError: NSError? since we don’t need it anymore. Instead of performing the conversion from the JSON to data and then checking for an error, we try to perform the conversion then catch an error, if one occurs. We’ll come back to how we’ll handle this error in a bit since Alamofire just added a nice way to do it.

Next we’ll tackle the updates to Alamofire.

Alamofire Migration

Alamofire has a migration guide for updating to version 2.0 since it includes a few changes that will break existing code.

The biggest change that we need to deal with is the new Result type. It’s used instead of having a result and an error listed separately in the completion handler signatures. For example, in version 1.3 this is the responseString function:


public func responseString(encoding: NSStringEncoding? = nil,
  completionHandler: (NSURLRequest, NSHTTPURLResponse?, String?, NSError?) -> Void) -> Self  {
        return response(serializer: Request.stringResponseSerializer(encoding: encoding), completionHandler: { request, response, string, error in
            completionHandler(request, response, string as? String, error)
        })
    }

Now the Result<String> replaces the String?, NSError? arguments in the completion handler and the NSURLRequest has become optional:


public func responseString(encoding encoding: NSStringEncoding? = nil,
  completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<String>) -> Void) -> Self
{
    return response(
        responseSerializer: Request.stringResponseSerializer(encoding: encoding),
        completionHandler: completionHandler
    )
}

So we’ll need to update our .responseX calls and fuctions to use the Result struct and NSURLRequest?.

Our only .responseX function that we use is .responseArrayAtPath. Let’s update that to use the new Result type (in AlamofireRequest_ResponseJSON.swift). Here’s what the signature looks like now:


public func responseArrayAtPath<T: ResponseJSONObjectSerializable>(pathToArray: [String]?, completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self {

We need to replace [T]?, NSError? with the new Result type and make the NSURLRequest optional:


public func responseArrayAtPath<T: ResponseJSONObjectSerializable>(pathToArray: [String]?, completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<[T]>) -> Void) -> Self {

Result is a generic type so we need to tell it what kind of result it will be holding if the serialization succeeds. Here’s it’s an array of ResponseJSONObjectSerializable objects, which we represent by T since the signature specifies the protocol responseArrayAtPath<T: ResponseJSONObjectSerializable>. So it’s written as Result<[T]> in the signature for the completion handler.

Now we have to update the function to actually use the Result. First let’s deal with that empty catch statement:


let jsonData: AnyObject?
do {
  jsonData = try NSJSONSerialization.JSONObjectWithData(responseData, options: [])
} catch  {
  return .Failure(responseData, error)
}

There are 2 cases for Result: .Failure and .Success. The .Failure case holds 2 properties: data and an error. So if NSJSONSerialization.JSONObjectWithData throws an error we can bubble it up for the caller to deal with it.

For the success case:


return (objects, nil)

Becomes:


return .Success(objects)

And we need to handle the case where we don’t get any response data. Currently it returns (nil, nil):


let responseSerializer = GenericResponseSerializer<[T]> { request, response, data in
  if let responseData = data {
    ...
  }
  return (nil, nil)
}

In Swift 2.0 we can improve that flow by using guard to check for requirements early. If we look at Alamofire’s stringResponseSerializer we can see how that’s done:


public static func stringResponseSerializer(
        var encoding encoding: NSStringEncoding? = nil)
        -> GenericResponseSerializer<String>
  {
      return GenericResponseSerializer { _, response, data in
          guard let validData = data else {
              let failureReason = "String could not be serialized because input data was nil."
              let error = Error.errorWithCode(.StringSerializationFailed, failureReason: failureReason)
              return .Failure(data, error)
          }

          ...

          if let string = NSString(data: validData, encoding: actualEncoding) as? String {
              return .Success(string)
          } else {
              let failureReason = "String could not be serialized with encoding: \(actualEncoding)"
              let error = Error.errorWithCode(.StringSerializationFailed, failureReason: failureReason)
              return .Failure(data, error)
          }
      }
  }

Let’s do something similar in our serializer. We can use Alamofire’s Error.errorWithCode convenience function since they have an error type that’s appropriate:


let responseSerializer = GenericResponseSerializer<[T]> { request, response, data in
  guard let responseData = data else {
    let failureReason = "Array could not be serialized because input data was nil."
    let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
    return .Failure(data, error)
  }
  
  let jsonData: AnyObject?
  do {
    jsonData = try NSJSONSerialization.JSONObjectWithData(responseData, options: [])
  } catch  {
    return .Failure(responseData, error)
  }
  
  let json = SwiftyJSON.JSON(jsonData!)
  
  var currentJSON = json
  if let path = pathToArray {
    for pathComponent in path {
      currentJSON = currentJSON[pathComponent]
    }
  }
  
  var objects: [T] = []
  for (index, item) in currentJSON {
    if let object = T(json: item) {
      objects.append(object)
    }
  }
  return .Success(objects)
}

Now that compiles but there’s a warning on this line:


for (index, item) in currentJSON {

Since we aren’t actually using the index we can replace it with _:


for (_, item) in currentJSON {

That’s it for the response serializer. Now in our StockQuoteItem.swift file we need to update where we call responseArrayAtPath so it matches our changes. Here’s the current version:


class func getFeedItems(symbols: [String], completionHandler: ([StockQuoteItem]?, NSError?) -> Void) {
  Alamofire.request(.GET, self.endpointForFeed(symbols))
    .responseArrayAtPath(["query", "results", "quote"], completionHandler:{ (request, response, stocks: [StockQuoteItem]?, error) in
      completionHandler(stocks, error)
  })
}

We need to change the completion handler:


.responseArrayAtPath(["query", "results", "quote"], completionHandler:{ (request, response, result: Result<[StockQuoteItem]>) in

And we might as well use the Result in the getFeedItems completion handler:


class func getFeedItems(symbols: [String], completionHandler: (Result<[StockQuoteItem]>) -> Void) {
  Alamofire.request(.GET, self.endpointForFeed(symbols))
    .responseArrayAtPath(["query", "results", "quote"], completionHandler:{ (request, response, result: Result<[StockQuoteItem]>) in
      completionHandler(result)
  })
}

One more change left to get the project to compile, we need to update the call to getFeedItems in StocksDataController. First the signature:


StockQuoteItem.getFeedItems(symbols, completionHandler:{ (results) in
  ...
}

And we need to pull the error and array of stock quote items out of the Result:


func loadStockQuoteItems(completionHandler: (NSError?) -> Void) {
  let symbols = ["AAPL", "GOOG", "YHOO"]
  StockQuoteItem.getFeedItems(symbols, completionHandler:{ (result) in
    if let error = result.error as? NSError {
      completionHandler(error)
      return
    }
    if let stocks = result.value {
      for stock in stocks { // because we're getting back a Swift array but it's easier to do the PageController in an NSMutableArray
        self.dataObjects.addObject(stock)
      }
    }
    // success
    completionHandler(nil)
  })
}

App Transport Security in iOS 9

Save and run. You shouldn’t see any more errors or warnings from the compiler.

There is one more thing that we need to handle. Change the Deployment Target to iOS 9 to build your app against the latest SDK. Don’t worry, it’ll still work on iOS 8 devices:

Change deployment target to iOS 9

But now we see an error on launch:

App Transport Security Error

This error is due to App Transport Security, a new feature in iOS 9.

It looks like Yahoo’s server doesn’t have its SSL implementation up to Apple’s standards. So we’ll have to add an exception to App Transport Security for that server. While we could just disable App Transport Security it’s much more secure to only create an exception for the one server that we need to access.

To create the exception we’ll need to add some keys to the info.plist. We’ll add an NSAppTransportSecurity dictionary. It’ll contain an NSExceptionDomains dictionary with a dictionary for the yahoo server: query.yahooapis.com. Within the query.yahooapis.com dictionary we’ll have a boolean entry NSThirdPartyExceptionAllowsInsecureHTTPLoads set to YES:

App Transport Settings in info.plist

Here’s the final code for Swift 2.0, iOS 9, and Alamofire 2.0.0: grokRESTPageViewController swift-2.0 branch.

Alamofire 3.0.0

While the response serialization in Alamofire 2.0.0 was a big improvement over previous versions it had a few limitations. So 3.0.0 was released soon afterwards. Unfortunately it made a few breaking changes so we need to update the code to fix it.

Update the Podfile to use Alamofire 3.0.0:


source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
pod 'Alamofire', '~> 3.0.0'
pod 'SwiftyJSON', '~> 2.3.0'

And run pod install.

Now when you try to run the project you will get a few errors.

One of the changes in Alamofire 3.0.0 is that you no longer need to cast errors in the result struct. Instead you’ll specify the error type. So first let’s get rid of the error cast we have in StocksDataController.swift. Change:


if let error = result.error as? NSError

to:


if let error = result.error

The signature for custom repsonse serializers has changed in Alamofire 3.0.0. So we’ll have to update our responseArrayAtPath function:


public func responseArrayAtPath<T: ResponseJSONObjectSerializable>(pathToArray: [String]?, completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<[T]>) -> Void) -> Self {
  let responseSerializer = GenericResponseSerializer<[T]> { request, response, data in
    // ...
  }
  
  return response(responseSerializer: responseSerializer,
    completionHandler: completionHandler)
}

To:


public func responseArrayAtPath<T: ResponseJSONObjectSerializable>(pathToArray: [String]?, completionHandler: Response<[T], NSError> -> Void) -> Self {
  let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
    // ...
  }
  
  return response(responseSerializer: responseSerializer,
    completionHandler: completionHandler)
}

The changes are:

  • Completion handler signature changed from NSURLRequest?, NSHTTPURLResponse?, Result<[T]> to Response<[T], NSError>. The bits in the various arguments in the old version are all packed up in the Response struct now. And we’re specifying that our error type is NSError.
  • GenericResponseSerializer has become ResponseSerializer. Again we need to specify that the error type is NSError so we have <[T], NSError> instead of just <[T]>. That’s what let us not have to unwrap the error in our StocksDataController.

Now for the body of the response serializer. Another improvement in Alamofire 3.0.0 is that we can use response serializers inside of other response serializers. So instead of using NSJSONSerialization.JSONObjectWithData to convert the resulting data to JSON we can use:


let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)

Here’s our old response serializer’s body:


let responseSerializer = GenericResponseSerializer<[T]> { request, response, data in
  guard let responseData = data else {
    let failureReason = "Array could not be serialized because input data was nil."
    let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
    return .Failure(data, error)
  }
  
  let jsonData:AnyObject?
  do {
    jsonData = try NSJSONSerialization.JSONObjectWithData(responseData, options: [])
  } catch  {
    return .Failure(responseData, error)
  }
  
  let json = SwiftyJSON.JSON(jsonData!)
  
  var currentJSON = json
  if let path = pathToArray {
    for pathComponent in path {
      currentJSON = currentJSON[pathComponent]
    }
  }
  
  var objects: [T] = []
  for (_, item) in currentJSON {
    if let object = T(json: item) {
      objects.append(object)
    }
  }
  return .Success(objects)
}

After using the JSONResponseSerializer we’ll use a switch statement to check for success or failure then proceed to create our array of objects instead of our do-catch statement:


let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
  guard error == nil else {
    return .Failure(error!)
  }
  guard let responseData = data else {
    let failureReason = "Array could not be serialized because input data was nil."
    let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
    return .Failure(error)
  }
  
  let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
  let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
  
  switch result {
  case .Success(let value):
    let json = SwiftyJSON.JSON(value)
    var currentJSON = json
    if let path = pathToArray {
      for pathComponent in path {
        currentJSON = currentJSON[pathComponent]
      }
    }
    var objects: [T] = []
    for (_, item) in currentJSON {
      if let object = T(json: item) {
        objects.append(object)
      }
    }
    return .Success(objects)
  case .Failure(let error):
    return .Failure(error)
  }
}

We’ve also added an early check for errors that occur before the serializer is called. Alamofire used to hide errors from the serializers if it already had an error (e.g., from a 404 response). Now you can decide whether to do an early return from your serializer to show those errors.

Finally we need to fix the code that calls responseArrayAtPath since we changed its signature. Here’s the current version:


class func getFeedItems(symbols: [String], completionHandler: (Result<[StockQuoteItem]>) -> Void) {
  Alamofire.request(.GET, self.endpointForFeed(symbols))
    .responseArrayAtPath(["query", "results", "quote"], completionHandler:{ (request, response, result: Result<[StockQuoteItem]>) in
      completionHandler(result)
  })
}

We can leave its signature the same other than specifying our error type is NSError (using the Result struct). We do need to change the responseArrayAtPath completion handler signature to use the Response struct. We can get the Result struct by calling response.result:


class func getFeedItems(symbols: [String], completionHandler: (Result<[StockQuoteItem], NSError>) -> Void) {
  Alamofire.request(.GET, self.endpointForFeed(symbols))
    .responseArrayAtPath(["query", "results", "quote"], completionHandler:{ (response: Response<[StockQuoteItem], NSError>) in
      completionHandler(response.result)
  })
}

And That’s All

Save and run. You shouldn’t see any more errors or warnings from the compiler or App Transport Security. Our app is now updated for Swift 2.0, iOS 9, and Alamofire 3.0.0.

Here’s the final code: grokRESTPageViewController alamofire-3.0 branch.

If you’d like more Swift tutorials on topics like this one, sign up below to get them sent directly to your inbox.