Monday, June 9, 2014

Access REST Web Service with Apple’s new Swift Language

In Apple’s own words, “Swift is an innovative new programming language for Cocoa and Cocoa Touch”.  From what I have seen so far I would agree with Apple’s description.  In this post I will show how easy it is to access a REST based Web Service with Swift by writing a simple iTunes search API.

So lets get started.  You will need XCode beta 6 to work with Swift so if you have not already downloaded/installed it you will need to do so now.   Once you have XCode 6 beta on your machine, start a new project and select the Single View Application template.


On the next option screen, enter the name of your project and then select Swift as the language.



Finally select the location of your project and now you are ready to start. 

We will begin by creating a ITunesSearchAPI class that will be used to make requests to Apple’s iTunes search API.  In this class we will define a Protocol that will be used to return the results of our search.  Lets start by defining our protocol:

protocol ITunesSearchAPIProtocol {
    func didRecieveResponse(results: NSDictionary)
}

The ITunesSearchAPIProtocol contains one function.  This function will receive an NSDictionary object that contains the results of our request to iTunes.

Now lets start the ITunesSearchAPI class.  We will start off by defining two properties like this

class ITunesSearchAPI: NSObject {
    var data: NSMutableData = NSMutableData()
    var delegate: ITunesSearchAPIProtocol?
}

These properties are the data property used to store the data coming back from the server and the delegate property that is used to define our ITunesSearchAPIProtocol.  Now lets create a searchItunesFor function that we will call to perform our search (Note this code has been updated for Swift 1.2, I have noted where changes were made form the original code).

func searchItunesFor(searchTerm: String) {
        
        //Clean up the search terms by replacing spaces with +
        var itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ",withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch,range: nil)
        
        // Changed for Swift 1.2
        // var escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
        if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
            
            var urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music"
            
            // Changed with Swift 1.2
            //  var url: NSURL = NSURL(string: urlPath)
            if let url = NSURL(string: urlPath) {
                var request: NSURLRequest = NSURLRequest(URL: url)
                
                //changed with Swift 1.2
                //  var connection: NSURLConnection = NSURLConnection(request: request,delegate: self,startImmediately: false)
                if let connection = NSURLConnection(request: request,delegate: self,startImmediately: false) {
                    
                    println("Search iTunes API at URL \(url)")
                    
                    connection.start()
                }
            }
        }

    }

The first two lines clean up the search term string that was passed into the searchItunesFor function so it can be used to create our search URL.  We then use this URL to create a NSURL object.  That NSURL object is used to create an NSURLRequest object and finally the NSURLRequest is used to create a NSURLConnection object.  Notice we set the startImmediately parameter, of the NSURLConnection, to false, this prevents the request from starting immediately.  If there is no additional code needed, you can set this to True to make the request run immediately but I like setting it to false and manually starting the connection.  This allows me to initialize anything needed prior to the request being made.  The last line is what manually starts the NSURLConnection.

Now lets create the NSURLConnectionDataDelegate functions.  Here is the first function which is called if the connection fails:

    //NSURLConnection Connection failed
    func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
        println("Failed with error:\(error.localizedDescription)")
    }

This next function is called when a new connection is established.  In this function we simply clear the data property.

    //New request so we need to clear the data object
    func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response:NSURLResponse!) {
        self.data = NSMutableData()
    }
   
This function is called each time data is received from the server.  In this function we append the new data to our data property.

    //Append incoming data
    func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
         self.data.appendData(data)
    }

This last function is called after all information is received from the server.  In this function we convert the data received from the server to a NSDictionary object.  We then call the delegate’s didReceiveResponse function with the NSDictionay object.  (Note this code has been updated for Swift 1.2, I have noted where changes were made form the original code)
   
    //NSURLConnection delegate function
    func connectionDidFinishLoading(connection: NSURLConnection!) {
        //Finished receiving data and convert it to a JSON object

        //Changed with Swift 1.2
        // var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data,options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data,options:NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
        
        delegate?.didRecieveResponse(jsonResult)
    }


Now lets look at how we would use the ITunesSearchAPI class.  The first thing we need to do is to add the ITunesSearchAPIProtocol to the list of protocols that the ViewController adopts.  To do this, simply add the ITunesSearchAPIProtocol to the comma-separated list following the name of the class’s super class like this: 

class ViewController: UIViewController, ITunesSearchAPIProtocol

In the apps ViewController.swift file add one property like this:

var api: ITunesSearchAPI = ITunesSearchAPI()

This will set the api property to our ITunesSearchAPI type and initiate it.   We then want to add the following two lines to the viewDidLoad function.

api.delegate = self;
api.searchItunesFor("Jimmy Buffett")

These two lines sets the delegate of our api property to this object and then calls the searchItunesFor function passing it the string “Jimmy Buffett” as the search term. 

Finally add our delegate function like this:

func didRecieveResponse(results: NSDictionary) {
    println(results)
}

This function simply logs the response to the console.  In future posts we will expand this function to actually do something with the data.

The full ITunesSearchAPI.swift file should look like this:


import UIKit

protocol ITunesSearchAPIProtocol {
    func didRecieveResponse(results: NSDictionary)
}

class ITunesSearchAPI: NSObject {
    var data: NSMutableData = NSMutableData()
    var delegate: ITunesSearchAPIProtocol?
   
    //Search iTunes
    func searchItunesFor(searchTerm: String) {
       
        //Clean up the search terms by replacing spaces with +
        var itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+",
                          options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
       
        var escapedSearchTerm =                         itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
        var urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music"
        var url: NSURL = NSURL(string: urlPath)
        var request: NSURLRequest = NSURLRequest(URL: url)
        var connection: NSURLConnection = NSURLConnection(request: request, delegate: self,
                          startImmediately: false)
       
        println("Search iTunes API at URL \(url)")
       
        connection.start()
    }
   
    //NSURLConnection delegate method
    func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
        println("Failed with error:\(error.localizedDescription)")
    }

    //NSURLConnection delegate method
    func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
        //New request so we need to clear the data object
        self.data = NSMutableData()
    }
   
    //NSURLConnection delegate method
    func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
        //Append incoming data
        self.data.appendData(data)
    }
   
    //NSURLConnection delegate method
    func connectionDidFinishLoading(connection: NSURLConnection!) {
        //Finished receiving data and convert it to a JSON object
        var err: NSError
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data,
                          options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary

        delegate?.didRecieveResponse(jsonResult)
    }

}


The full ViewController.swift file should look like this:

import UIKit

class ViewController: UIViewController, ITunesSearchAPIProtocol{
   
    var api: ITunesSearchAPI = ITunesSearchAPI()
   
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       
        api.delegate = self;
        api.searchItunesFor("Jimmy Buffett")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func didRecieveResponse(results: NSDictionary) {
        // Store the results in our table data array
        println(results)
    }

}


Part 2:  In part 2 we will expand on the code presented here to display the results, with album covers, in a UITableView.  We will have two requirements for loading images, they must be loaded in the background so the UI does not freeze while the images load and to create an image cache so we do not reload images we already have. To read part 2, follow this link Access REST Web Service with Apple’s new Swift Language - Part 2

15 comments:

  1. Hi Jon,

    Thanks for this post.
    Can you please help in integrating Reachability class with Swift programming for network check.

    ReplyDelete
    Replies
    1. I have seen a number of different Reachability class for iOS/OS X written in Objective-C. There is even a Reachability class in my book. I would recommend integrating one of them with Mix and Match like I documented in this post about using the RNCryptor library with Swift.

      Delete
  2. Hi Jon,
    Is there something missing from this half of the tutorial? I don't think tableData has been populated with the returned results. In my code I added this to my didReceiveResponse method: tableData = results["results"] as NSArray

    I still have something else wrong, with my IB connections, since my table protocol methods aren't getting called to populate the table.

    ReplyDelete
    Replies
    1. Usually the main reason for table protocol methods not being call is when I forget to connect the tableview to the dataSource and delegate. Are these set similar to this: http://i.stack.imgur.com/f4t7Y.png
      I did not walk though how to connect these, I just said " In the view for the ViewController add a UITableView and then set the dataSource and delegate like you did with Objective-C." so my first thought is you may of missed this.

      Delete
    2. I have the same issue as Timothy. I've connected the tableview to the dataSource and delegate as you do in your image. I notice that the function returning tableData.count is called before the didReceiveResponse function. At that point tableData is empty so it returns 0. Is that a problem?
      None of the other table protocol methods are being called.

      Delete
    3. I believe I found the issue and I updated part 2 of the tutorial with the fix. I forgot to mention that we needed to update the didRecieveResponse function. It should look like this:

      func didRecieveResponse(results: NSDictionary) {
      // Store the results in our table data array
      println("Received results")
      if results.count>0 {
      self.tableData = results["results"] as NSArray
      self.appsTableView.reloadData()
      }
      }

      Sorry for the confusion.

      Delete
    4. Thanks, Jon! I was missing the reloadData call. And I had to connect the appsTableView outlet to the TableView in the .xib file

      Delete
    5. Thanks Jon. Your change to didRecieveResponse was the last piece in my puzzle. I did get better results by changing the search from Jimmy Buffet to "Weird Al" Yankovic. ;)

      Delete
  3. You know the app wsbroadcast.com ? You create your services , entities, and it creates the WS interfaces automatically.

    ReplyDelete
  4. Hi Jon,

    Congratulations for your tutorial is very interesting and easy to understanding, especially for users like me who are starting in IOS world. Sorry about my ignorance, I am using XCode version 6.3.2 and I am getting some sintax errors how you can see in my shared image:

    https://goo.gl/photos/uLppVkib99CKypky7

    Do you have any ideia whats is going on?

    Thks in advance

    ReplyDelete
  5. I have updated the examples on the github page to reflex the changes in Swift 1.2 but I forgot to make the changes here. Please have a look at this github page for now and will will update the tutorial in the next couple of days: https://github.com/hoffmanjon/RSNetworking

    Sorry for the confusion

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Thanks for your prompt response and sharing your knowledge with us!

      I checkout the github project and works fine. The source code of new version is very different from this version. I will wait for your update in blog to understanding the code behavior.

      Regards

      Delete
    3. You are welcome, I updated the code for Swift 1.2 and also noted were the changes occurred. Please let me know if you have any questions.

      Delete