Want Swift news and tutorials delivered to your inbox?
Subscribe now and I'll start sending you updates.
No Spam. Ever.

Developing iOS Apps Using Swift Part 3 – Best Practices

 

This section completely updated to reflect changes in Xcode 6.3, as of April 16, 2015

In parts 1 & 2 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them. If you haven’t read that yet, check out Part 1 and Part 2. If you enjoy these tutorials, I’m also working on a book full of quality content for Swift developers, and it’s available for Pre-Order now.

In this section we’re going to take a stop and clean up some of the code we’ve created so far by removing our network logic from our view controller code, and fixing some other issues that will hurt our performance. This may not be the most glamorous part of writing a new app, but it’s important! Let’s do it…

If you’re looking something that goes a little deeper than these tutorials, I also have written a book and created video tutorials which are available now. If that’s more your speed, check out my Swift Book & Video Packages ».


Get The Swift Book
Learn About My Book & Video Packages »

Split up the code

First things first, let’s rename our View Controller to be something more meaningful. Open up your ViewController.swift file and replace the class name ‘ViewController’ with our new name, ‘SearchResultsViewController’. Rename the file as well to SearchResultsViewController.swift.

If you try to run the app from here you’ll get a crash. This is because our storyboard file hasn’t been updated to know about the new class! So open up the Main.storyboard, select your ‘View Controller’ object in the scene (the left-hand side nav), and then select the Identity Inspector (right-hand side, third button.)

From here let’s change the class from ‘ViewController’ to ‘SearchResultsViewController’. Now we should be back on track. Check that the project still works, and let’s proceed.

Let’s now move the API code out to it’s own class. Right click in the xcode navigation pane and select ‘New File…’. This one is going to be a regular Swift file, under the iOS->Source navigation option.

This one handles all API work, so I’m going to call it APIController.

Now let’s grab our searchItunesFor() function, and cut and paste it out of the Search controller and in to the APIController, inside of a new class ‘APIController’.

Your complete APIController.swift file should look something like this:

import Foundation

class APIController {
    
    func searchItunesFor(searchTerm: String) {
        
        // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
        let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
        
        // Now escape anything else that isn't URL-friendly
        if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
            let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
            let url = NSURL(string: urlPath)
            let session = NSURLSession.sharedSession()
            let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
                println("Task completed")
                if(error != nil) {
                    // If there is an error in the web request, print it to the console
                    println(error.localizedDescription)
                }
                var err: NSError?
                
                var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
                if(err != nil) {
                    // If there is an error parsing JSON, print it to the console
                    println("JSON Error \(err!.localizedDescription)")
                }
                let results: NSArray = jsonResult["results"] as NSArray
                dispatch_async(dispatch_get_main_queue(), {
                    self.tableData = results
                    self.appsTableView!.reloadData()
                })
            })
            
            task.resume()
        }
    }
    
}

If you try to build this straight away you’ll see three errors:

1) searchItunesFor() is now undefined in our SearchResultsViewController.
2) self.tableData is now undefined in the APIController.
3) self.appsTableView is now undefined in the APIController.

To deal with this we need to let these objects know about each other. So let’s make APIController a child object of our SearchResultsViewController. It’s pretty easy to do, just add the following line underneath your SearchResultsViewController class definition:

let api = APIController()

Now modify the line that calls searchItunesFor() to this:

api.searchItunesFor("Angry Birds")

The only difference here is that we’re calling the method from an instance of an APIController, as opposed to doing this all from a method in the View Controller.

That takes care of the first set of errors, but now we need to also get APIController’s results back to the SearchResultsViewController. We would like for our API controller to be able to respond to arbitrary API calls, so we should define a protocol that views can subscribe to in order to get results!

Defining an API protocol

There are two erroring lines in our APIController right now referencing the tableData results, let’s just remove these. We’re going to use something a little cleaner.

dispatch_async(dispatch_get_main_queue(), {  // DELETE ME
    self.tableData = results                 // DELETE ME
    self.appsTableView!.reloadData()         // DELETE ME
})                                           // DELETE ME

Above the class definition in our APIController, let’s add a protocol that declare the function didReceiveAPIResults() as a required implementation. Note that the protocol goes outside of the class definition, so make sure it’s outside of the APIController{} curly braces.

 

protocol APIControllerProtocol {
   func didReceiveAPIResults(results: NSArray)
}

 

This doesn’t do anything on it’s own, but now we can add the protocol to our SearchResultsViewController. Not adhering to the protocol will now cause an error, so we don’t make the silly mistake of not implementing didReceiveAPIResults!

Adhering to the protocol

Now modify your SearchResultsViewController to adhere to the protocol:

class SearchResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, APIControllerProtocol {

Building now should give an error that SearchResultsViewController doesn’t conform to the APIControllerProtocol, great! This is exactly what a protocol does. It causes the compiler to spit out errors if you intended to implement a protocol, but didn’t completely do it. In this case it’s barfing because we’re missing the didReceiveAPIResults() callback method. Let’s add it…

We just need to add the function within our SearchResultsViewController class. It will look pretty much like the contents of our dataTaskWithURL closure from before.

func didReceiveAPIResults(results: NSArray) {
    dispatch_async(dispatch_get_main_queue(), {
        self.tableData = results
        self.appsTableView!.reloadData()
    })
}

The one thing left to do is change our API Controller to include a delegate object, and to call this method when the connection finished loading some API data.

Using the Protocol

Back in our APIController.swift file, let’s add the delegate and an empty constructor, just under the class definition.

var delegate: APIControllerProtocol?

The question mark at the end here indicates that delegate is an *optional* value. Without the question mark, we will get a compiler error for not using an initial value for this variable, but with it we’re okay. The delegate object can be of any class here, as long as it adheres to the APIControllerProtocol by defining the method didReceiveAPIResults(), as we have done in our SearchResultsViewController

Now that we have the delegate variable added, let’s head back to our SearchResultsViewController, and in the method viewDidLoad, let’s set the delegate for our api controller to itself, so it will receive delegate function calls.

api.delegate = self

Finally, in the dataTaskWithURL closure inside of our searchItunesFor() method, let’s add our protocol method where our tableview reloading code used to be. This will pass the responsibility of reloading the UI to the delegate, which in this case is SearchResultsViewController.

This goes in place of where we delete the lines refreshing the tableview earlier on.

if let results: NSArray = jsonResult["results"] as? NSArray {
    self.delegate?.didReceiveAPIResults(results)
}

Here is the completed APIController.swift code up to this point:

import Foundation

protocol APIControllerProtocol {
    func didReceiveAPIResults(results: NSArray)
}

class APIController {
    var delegate: APIControllerProtocol?
    
    func searchItunesFor(searchTerm: String) {
        // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
        let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
        
        // Now escape anything else that isn't URL-friendly
        if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
            let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
            let url = NSURL(string: urlPath)
            let session = NSURLSession.sharedSession()
            let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
                println("Task completed")
                if(error != nil) {
                    // If there is an error in the web request, print it to the console
                    println(error.localizedDescription)
                }
                var err: NSError?
                if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {
                    if(err != nil) {
                        // If there is an error parsing JSON, print it to the console
                        println("JSON Error \(err!.localizedDescription)")
                    }
                    if let results: NSArray = jsonResult["results"] as? NSArray {
                        self.delegate?.didReceiveAPIResults(results)
                    }
                }
            })
            
            // The task is just an object with all these properties set
            // In order to actually make the web request, we need to "resume"
            task.resume()
        }
    }
    
}

Common issue here

*** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key appsTableView.’

If you get this issue, it could be caused by a few bugs in Xcode surrounding the renaming of View Controller subclasses. The best way to fix this is to open up the storyboard, select the ‘Search Results View Controller’ object. Then in the identity inspector change its class back to UIViewController, build the app, then change it back to ‘SearchResultsViewController’. This resets some things and fixes the issue.

PHEW!

Okay, I know that might have seemed like a lot of boilerplate, and now our app does the exact same thing as it did before, but now we have something much more flexible. We can now use APIController for any API call to the iTunes search API, and have a custom delegate get the response. I think we ran out of time on this one, so we’ll move on to the interaction in Part 4. If you want to follow along make sure to sign up for them via e-mail.

I felt this step was more important than fixing the performance issues, but I promise we’ll speed things up before we’re done. There are just some things we need to get done first. Hold tight!

Full code for this section visible here.

Go to Part 4 ->

 

Follow me on Twitter


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS

34 thoughts on “Developing iOS Apps Using Swift Part 3 – Best Practices

  1. Thanks for the tutorial! I just wanted to point out one line of code you forgot to mention in your tutorial (but is in your Github code) that causes the app not to show the loaded data. The line api.delegate = self in viewDidLoad() needs to be in there in order for the api controller to call the controller’s implemented didReceiveAPIResults method.

    Thanks!

    • Holy shit I was about to tear my hair out figuring out why the APIController wasn’t sending back the info correctly, even though the dict info was correctly being stored, thank you for the tip~!

  2. First off, thank you for this tutorial.

    Since this is entitled best practices, this is probably the best place fro my question.

    Take the func searchItunesFor, shouldn’t all of the var’s in this routine be let’s?

    If I’m reading the Swift documentation correctly (which hey I’m learning, so I might not be) one should use let unless one actually changes the value. This being helpful to the compiler and general code safety.

  3. Again, since this is the best practices section.

    Curious about opinions on readability/style

    var api: APIController = APIController()
    versus
    var api = APIController()

    (or as per previous comment
    let api = APIController()
    )

  4. I get APIController not constructible with ‘0’
    (on line var api: APIController = APIController() )

  5. First off, thanks for the great series. And as a sucker for clean and modular code, I love this part of the tutorial. Keep them coming :)

  6. I want to say that I like this tutorial very much. But when I get to the point

    “Let’s now move the API code out to it’s own class. Right click in the xcode navigation pane and select ‘New File…’. This one is going to be a Cocoa Touch Class, under the iOS->Source navigation option.

    This one handles all API work, so I’m going to call it APIController.

    Now let’s grab our searchItunesFor() function, and all our delegate functions like connection(didRecieveResponse….). Cut and paste these out of the Search controller and in to the APIController. You’ll also need to copy over the data variable for the response to build it’s results.”

    This is when I get lost and everything on my files are giving me 9+ errors. I looked at your code and it doesn’t seem to fit what you said in tutorial. I thought it meant cut and paste said code into “APIController.swift” but those code remains in “SearchResultviewController.swift”…? I’m bit lost. Can you clarify?

    • Oh, and one more… when you said to make new file, you didn’t specify which type and I picked UIViewController and the result did not match your file as well. So I had to edit that out. Prior errors remains, however.

      • I did but I want to get better understanding of what’s happening so I can develop my own app. I’m doing a job relating to developing an app for an organization.

        Question: do you think it’s critical that I learn xcode program itself or swift language? I think I have good understanding of swift but handling xcode program is kinda sketchy… Is it just me? :)

        Also, thanks for your response!

  7. I am getting Thread 1- EXC_BAD_INSTRUCTION at delegate?.didReciveAPIResults();

    So far my guess is EXC_BAD_INSTRUCTION occurs if some uninitialized or unallocated object/variable is used for something. But as I am following your tutorial, and delegate is initialized in ViewController class, I think this shouldn’t be the case.

    What do you think, why am I getting this? Waiting for your reply

    Regards

  8. I’m getting this error:

    2014-06-09 10:12:39.101 CheckPlease[4594:218563] *** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key dataSource.’

    What gives?

  9. Can you tell me why when I comment out the dispatch_async in the didReceiveAPIResults, I don’t get any data. Why do you have to explicitly specify to Xcode that you need those lines sent immediately.

    Thanks

  10. I’ve tried to figure out why i’m getting this error. Even your Github code has the same error!

    SearchResultsViewController:
    self.api.delegate = self
    The error is: ‘APIController’ does not have a member named ‘delegate’

    Same are in the APIController for this line:
    self.delegate?.didReceiveAPIResults(jsonResult)

    Any Ideas what we’re doing wrong.

    Carl

    Ps pre-ordered your book.

      • Excellent, I had the line but placed it into the function and not above it. It’s always the simplest things that can just grind you to a halt.

        Thanks!

        Carl

        P.s In your book could you possible talk about the following as some suggestions:

        • Using Swift how can I set up the Tabs, Split view, Page view, Segmented Control & Navigation controllers. Then to possibly use a custom made navigation?
        •Using swift to set up table views & table cells – possibly link to core data/cloud kit and search.
        •Using the animation effects showing ways of implementation of swift and showing it’s different capabilities and visual effects to aid design.
        •Using swift to show how user controls can be done – Date Picker, Picker view, Steppers, Sliders and Scroll View?

        :-)

  11. It seems you’ve got a strong reference cycle between the SearchResultsViewController and the APIController. Neither of them will be automatically deleted once both are created and point to each other.

    Back in the old Objective C days, delegates were always supposed to be weak. Should that be the case here?

    (By the way, I’m really enjoying this tutorial. Great job!)

  12. I’m a beginner of iOS development and Swift, so your tutorial is very helpful. Thank you!!

    I typed your code and build it, but the app clashed at SearchResultsViewController’s “var api: APIController = APIController()” statement. I found the cause is that I write this before “@IBOutlet strong var appsTableView: …” statement.
    Do you know the differences?

  13. Is that possible to remove “let results: NSArray = jsonResult[“results”] as NSArray” in APIController.swift?

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>