Emerging Best Practices in Swift

With Swift’s release, we got the opportunity to reimagine solutions to problems we solved previously in Objective-C, but to do them right this time around. In this talk at the GOTO Conference CPH 2015, Ash Furrow explores how to navigate the uncharted waters of Swift to discover and identify new best practices, with concrete examples such as unit testing and refactoring code.


iOS 5 or Earlier? (04:39)

We have been in a similar situation to Swift before. Before iOS 5 we did not have object literals; this is how we created arrays, dictionaries, and NSNumbers:

NSArray *array = [NSArray arrayWithObjects: @"This",
    @"is",
    @"so",
    @"tedious", nil];

NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
@"Who would do this?", @"Not me", nil];

NSNumber *number = [NSNumber numberWithInt:401];

Arrays have this arrayWithObjects:, with this nil-terminated list of things; we also have dictionaryWithObjectsAndKeys: and numberWithInt:. It was tedious and, if you were doing this before ARC, it was even worse:

NSArray *array = [[NSArray arrayWithObjects: @"This",
    @"is",
    @"so",
    @"tedious", nil] retain];

NSDictionary *dictionary = [[NSDictionary dictionaryWithObjectsAndKeys:
@"Who would do this?", @"Not me", nil] retain];

NSNumber *number = [[NSNumber numberWithInt:401] retain];

Object Literals (05:59)

There is a mistake in the code above: in dictionaryWithObjectsAndKeys, the objects come first, so the key is “Not Me” and the value is “Who would do this?”, which is backwards from what you might expect. I am so glad we do not have to do that anymore. We have object literals: arrays are created with an @ and then square brackets, dictionaries with @ and curly braces, and NSNumbers are created using boxed expressions (put an @ and what comes after is evaluated as a number). Using object literals became “best practice.”

Blocks & GCD (07:08)

Blocks came out in 2010, along with iOS 4. When blocks came out, they had a weird syntax and were hard too adopt, but also iOS developers were used to solving problems without them. We were used to solving problems with protocols, and delegation, or target-action, or any number of ways. Blocks are just syntax. They enable other best practices. We can do functional reactor programming, or we can execute a block of code in a context, like an animation. We could also do callbacks when an asynchronous network response has been delivered. Collection operationsnow allow us to enumerate over a collection, like an array, or we could map it into an array of something else. These were all new ideas at the time. We have a history of taking the new technology that Apple has provided for iOS development and doing amazing things with it. That brings us to Swift 2.

Swift 2 (10:00)

Swift 2 is the second version of Apple’s programming language, first announced and released last year. Swift 2 includes new syntax that lets us do new things, but syntax is only a tool. These new things include guard statements, defer statements, and throwing with new error handling. These are syntax features, but there is not a best practice of throwing something, and using defer is not a best practice. Let’s take a look at a few examples:

Should I Use guard? (10:49)

if let thing = optionalThing {
    if thing.shouldDoThing {
        if let otherThing = thing.otherThing {
            doStuffWithThing(otherThing)
        }
    }
}

Swift 2 introduced compound if/let statements and where clauses. Now we have something like the code below. This is semantically identical code: we are binding two variables to optional variables, and we have a where clause that obviates the need for one of the if statements.

Get more development news like this

if let thing = optionalThing,
   let otherThing = thing.otherThing
   where thing.shoudDoThing {
    doStuffWithThing(otherThing)
}

Avoid Mutability (11:56)

func strings(
    parameter: [String],
    startingWith prefix: String) -> [String] {

    var mutableArray = [String]()
    for string in parameter {
        if string.hasPrefix(prefix) {
            mutableArray.append(string)
        }
    }     
    return mutableArray
}

This is the Swift code that I was afraid people were going to write: this is just Objective-C code but in Swift syntax. Instead we want to write something like this:

func strings(
    parameter: [String],
    startingWith prefix: String) -> [String] {

    return parameter.filter { $0.hasPrefix(prefix) }
}

This is the same function: it takes an array of strings and a prefix, and it returns you any of those strings that start with the prefix. Less typing, more expressive.

Currying (12:39)

Currying is a word you might have heard before, and been scared off by, but it’s actually a pretty straightforward concept. It’s really useful when you have two funcitons that do almost the same thing.

Before Currying

I have a function called containsAtSign. It takes a string, and returns a boolean. It returns true if that string contains an at sign:

func containsAtSign(string: String) -> Bool {
    return string.characters.contains("@")
}
...
input.filter(containsAtSign)

We can use this function with filter because functions are semantically identical to closures. Instead of passing a closure into filter, we just pass in the name of our function.

After Currying

func contains(substring: String) -> (String -> Bool) {
    return { string -> Bool in
        return string.characters.contains(substring)
    }
}
...
input.filter(contains("@"))

Now, instead of a containsAtSign function, we have a contains function. It takes a substring and it returns a closure. You can see that closure takes a thing called string and returns a Bool. It does the exact same thing as before, except now instead of an at sign literal, we are using the substring parameter passed into the original function. We can can call it like you see at the bottom here. Instead of having a function for containsAtSign, and a function for containsPercentSign, and a function for containsDollarSign, we just have one contains function. We give it the little bit of customization that it needs, and it does the rest for us.

The code below is a semantically identical expression. It goes from a substring and returns a closure that goes from String to Bool.

func contains(substring: String)(string: String) -> Bool {
    return string.characters.contains(substring)
}
...
input.filter(contains("@"))

Extract Associated Values (15:07)

Something that we had from Swift 1 was the ability to have associated values on enums and extract them later. When you create a enu, you can attach an associated value to it, and you extract that value through a case statement. In Swift 1, case statements were only available inside of a switch block. You would write code like this:

enum Result {
    case Success
    case Failure(reason: String)
}
switch doThing() {
case .Success:
      print("🎉")
case .Failure(let reason):
    print("Oops: \(reason)")
}

There’s a function called doThing which returns a Result enum. If the result is a success, then we print a little party time! If it is not, then we can extract the reason from the case and print it out.

Swift 2 lets us take case statements outside of switches:

enum Result {
    case Success
    case Failure(reason: String)
}
if case .Failure(let reason) = doThing() {
    print("😢 \(reason)")
}

If we only care when something goes wrong, then we can write code like this.

Syntax vs. Idea (16:22)

These are things that we can adopt easily. Are these best practices? No, just syntax. What does it matter that we can use case statements outside of switch statements? It is easier to do (which is nice); it might enable us to do things more easily or quickly. But what matters are the ideas. Swift 2 introduces new syntax that lets us do new things, and we need to look for the new things that it lets us do.

Apple has clearly looked at other languages and communities for things that could be brought over to Swift. But how can you tell if something in another language is a new idea, or just syntax? Well, you can’t. It’s a false dichotomy. Things are a little bit of both.

Never Throw Away Ideas (18:54)

When Swift came out, I thought we should use it all the time, that it was universally better. I was wrong. We shouldn’t throw away everything we had before. For example, Swift still doesn’t have a replacement for key-value observation, so we still need to use the Objective-C runtime for that. Older ideas have merit, so don’t throw ideas away.

iOS Developers Throw Things Away…a Lot (19:49)

Let’s talk about a beginner who’s never programmed before. They’re learning iOS and Objective-C. They’re bad at it, because begineers are bad at things (which is ok). They blame their frustration on Objective-C, and think that it must be bad. But eventually, they get better, and learn more.

Then, a new thing, Swift, comes out! Learning Swift is easier than learning Objective-C was. The former beginner thinks that the new thing must be better, but they don’t realize that they have more experience now. The programmer has mixed these two concepts up: how easy things are to learn, and how easy it is to learn once you have experience. This is why new ideas, like new APIs, are so appealing.

We always have a fresh new supply of old APIs to hate, thanks to Apple. This isn’t something to be ashamed of, but it’s good to acknowledge, and maybe question when it comes up.

Let’s Talk About Refactoring (22:26)

What is not a refactor?:

  • If you are adding new functionality
  • If you are changing the public interface for your type
  • If you are changing the behavior of a type

Anything else is a rewrite. This is a very simple test: Do you have to change a unit test? If you do, then that is a rewrite; if you do not, then you are actually refactoring something.

I read an article early in my career that had a big impact on me, Joel Spolsky’s Things You Should Never Do, Part I. He wrote it about Netscape, which is obviously different than a single view controller, but what is an application if not a collection of view controllers? If you should never rewrite applications, maybe we should be more careful about rewriting our view controllers.

We should favour incremental change. Throwing code away and replacing it with something brand new is dangerous, because you lack the context for how that code interacts with the rest of the code. That is why rewriting is terrible, because it will always take longer than you think and you will always make mistakes that you do not anticipate.

The two things that we should never throw away are code and ideas.

Unit Testing & Thinking (25:30)

First, let’s presume that unit testing is a good idea. The test themselves aren’t important. Writing tests forces me to think, especially about new best practices.

Benefits of Testing (26:06)

One of the biggest advantages to unit testing is that it limits the scope of your object (because you need to think about it). If you have a complex object that does many things, that’s difficult to test, so you are more likely to make several smaller objects, which do one thing each but do them well. Now you have more cohesive objects, and you do not have behavior that is coupled to each other. The best way to acheive this is to control your public interface, in conjunction with dependency injection.

Dependency Injection (28:01)

Dependency injection seems like a really scary topic, and I didn’t want to do it for a long time, because all the explanations were too complicated. But it’s actually simple, just a €5 word for a ¢5 idea.

The basic idea is that the thing that you’re making shouldn’t make the things that it needs to do its job. Let’s look at an example.

This is what I would say a pretty standard iOS view controller. It has a networking controller, and in viewdidLoad it fetches stuff from the network. Once that’s done, a completion block is invoked and it shows that stuff.

class ViewController: UIViewController {
    let networkController = NetworkController()

    func viewDidLoad() {
        super.viewDidLoad()
        networkController.fetchStuff {
            self.showStuff()
        }
    }
}

There’s nothing wrong with this view controller, but how does it look different with dependency injection?

class ViewController: UIViewController {
    var networkController: NetworkController?

    func viewDidLoad() {
        super.viewDidLoad()
        networkController?.fetchStuff {
            self.showStuff()
        }
    }
}

Do you see the change? We are no longer creating the network controller. Instead, we have an optional, variable network controller. Nothing in the rest of our class has changed. The only thing that is changed is the line that defines the network controller.

We are relying on someone else to create the network controller for us and give it to us. It could be another part of our app. Maybe in prepareForSegue, the view controller before us created a network controller for us, or maybe it passed in a reference to the one it had. Dependency injection is not that hard.

Protocols work well for this. Let’s take a look at a hypothetical network controller protocol:

protocol NetworkController {
    func fetchStuff(completion: () -> ())
}
...
class APINetworkController: NetworkController {
    func fetchStuff(completion: () -> ()) {
        // TODO: fetch stuff and call completion()
    }
}

It has a function, fetchStuff. It accepts a completion block that it calls when it’s done fetching. We have an API network controller, which conforms to the protocol, so it has the fetchStuff function.

protocol NetworkController {
    func fetchStuff(completion: () -> ())
}
...
class TestNetworkController: NetworkController {
    func fetchStuff(completion: () -> ()) {
        // TODO: stub fetched stuff
        completion()
    }
}

A test network controller conforms to the same protocol. Instead of fetching from the network, it uses stubbed data that I know ahead of time, and it immediately invokes the completion block. Now, I can configure the test controller, pass it into the view controller, ask the view controller to do something, and finally test its behavior.

I have a protocol that does whatever, which I’ll test independently later. I conform to that protocol in this test network controller. I do something for which I know the result ahead of time, and I know how I expect the view controller to react. Then, I test to make sure that reaction is what I expected.

The use of protocols helps you limit scope to a clearly defined contract about what your object is supposed to do. Adding a method to your protocol now becomes a decision. You do not have access to a class’s list of functions, so if it is not in the protocol you can’t call it. If you want to call it, you have to make the decision to add it to the protocol.

Shared State and Singletons (33:03)

Let’s say we have this function called loadAppSetup. If I have not launched before, then show the user something. It does this by grabbing something out of NSUserDefaults.standardUserDefaults(); if it has not happened yet then it calls some function.

func loadAppSetup() {
    let defaults =
        NSUserDefaults.standardUserDefaults()
    if defaults.boolForKey("launchBefore") == false {
        runFirstLaunch()
    }
}

How would you even test that? You are going to have to clear out the defaults and set them up for your specific test, and then make sure to clear them out afterwards. Dependency injection would have us inject the defaults into the function.

func loadAppSetup(defaults: NSUserDefaults = .standardUserDefaults()) {
    if defaults.boolForKey("launchBefore") == false {
        runFirstLaunch()
    }
}

This is now a dependency injection–friendly function. Now, if I do not specify a parameter it is going to fall back to the standardUserDefaults. In my unit tests, I can create my own user defaults that are not the standard ones and inject them in.

loadAppSetup() // In your app
loadAppSetup(stubbedUserDefaults) // In your tests

Nothing has changed from the rest of the application’s perspective, but in the unit tests now we can inject that in. Our tests become simpler and they become isolated, which is what you want in tests.

Let’s go back to our view controller example. Using the same default parameter cheat, I might have the networkController property set to an APINetworkController, but critically important here is the fact that I am specifying that it conforms to the protocol. The view controller still does not have access to the actual APINetworkController; it only has access to the protocol. Nothing else in the view controller has changed. You could test this now by creating the view controller and injecting a stubbed or mocked version of the NetworkController protocol. As long as you inject it before the view is loaded, then you can test your controller.

class ViewController: UIViewController {
    lazy var networkController: NetworkController =
        APINetworkController()

    func viewDidLoad() {
        super.viewDidLoad()
        networkController.fetchStuff {
            self.showStuff()
        }
    }
}

Back to Unit Testing (36:16)

Do not test private functions. Also, start marking your functions as private. By default they are internal, which means you can call them, but you should not be calling them. Avoid rewriting wherever possible, and do not test your implementation. Remember the goal of unit testing is to test the behavior of the public interface. In an ideal world, every object would only have one public function. Last week, Justin Searls had this amazing post about using partial mocks. A partial mock is something real, but also fake. In our example, we could not do it because we used protocols. For partial mocks, we would use subclassing, but we would only overwrite some of the methods.

Look for Abstractions (39:00)

Let’s try new things. Even if you try something and it does not work, you still understand that solution. I am encouraging you to experiment and try new things. You are going to write unit tests along the way. You are working at high level of abstraction.

Some people say there’s no time to experiment. It is crunch time, you’ve got a deadline, you have to ship to the App Store. You do not have time to write unit tests, you do not have time to explore, you do not have time to try new things.

But when you are used to experimenting and you are used to testing, it gets easier. Then you can experiment, and continue to experiment, and test when you do not have as much time. When you are done with the crunch time, you can go back and do it some more. There is always an opportunity to learn. We should take advantage of those opportunities.

Wrap Up (43:01)

As a community and as an industry, iOS community has a history of exploring new technologies: taking something that is cool and new, and building amazing things with it. We have done it with Objective-C, and now Swift. Now is not the time to stop looking for cool, new solutions. Learning is not just for when Xcode is in beta. Effective unit tests makes your code easier to change incrementally. Finally, if you can operate at the highest level of abstraction you can at any given time then when you cannot operate at that high level you can fall back to something that is still better than where you were.

Fun talk for me to research and think. Thank you!

Q&A (32:05)

Q: If we cannot pass in objects, how would you pass an object to the view controller’s view model? Ash: It is tricky and it comes down to personal preference. In my opinion, I like the view model that is created by the view controller in a lazy closure block. If a test is being run, then it can override that property so that the real view model is never created, so that the view controller only ever uses test view models in unit tests. The view model itself to be created you can have it created by a different view model, or you can have it created by the view controller that is presenting this one. It does not matter; I encourage you to experiment and see what you like best.

Q: If you would start a new iOS project tomorrow, would you write it in Swift or Objective-C? Ash: The only reason I can think of for choosing Objective-C would be having a team of developers with no Swift experience and a tight deadline.

Q: What did square brackets do to offend you so much? Ash: Square brackets did not do anything to offend me. I do not hate square brackets or semicolons. Square brackets make certain types of programming (e.g., functional reactive programming) more verbose than they need to be.

Q: You talked about doing a lot experiments, and some of them, of course, being end of production and somebody has to maintain them for a very long time afterwards. If everybody’s experimenting all the time then you will have a fun time producing code I think. Ash: That is true. I guess I should have stipulated that if an experiment does not work out the way you think than you should throw it away, or at least not merge it into master.

Q: We are talking about, I suspect, feature branch experiments, not like “let’s do weird stuff and see what happens.” Ash: Exactly. I’m not like the spaghetti against the wall approach, or anything like that. Ideally, you would be working on experiments in a playground, not in a production application. But that’s where I do it!

Q: There was a exciting slide where you said, “Because of dependency injection, I am able to write tests for my code, and I also write my code in a better way.”” And then, you cheated and just actually did not change your actual code at all. I was a bit let down, so can you comment? Ash: I did not change the code at all because the whole class was like eight lines and there is not a lot to change. I was trying to show how simple it was to do a dependency injection and this is kind of a cheat, but I was focusing on the pedagogical example of teaching how to do dependency injection, and not how to write better code with unit tests. In that specific instance, it would not be the view controller necessarily that would have been changed. It is the network controller, the thing that does the networking request. If the view controller can only access it through the clearly definied protocol, then it is less likely to call the internal functions that it should not be aware of, and that way you have very loose coupling. The view controller and the network controller do not know about each other, except through explicitly defined and limited ways. They do not do things like the network controller is not able to, like, add a subview. The view controller is not able to parse JSON. Those are two different separate concerns and they should be in separate classes and their communication to each other should be very constricted. I think maybe in a future version of this talk maybe one that is instead of calling it, Merging Best Practices, if I actually have a talk on unit testing, maybe I would go into more detail about that.

Ash Furrow

Ash Furrow is a Canadian iOS developer and author, currently working at Artsy. He has published four books, built multiple apps, and is a contributor to the open source community. He blogs about a range of topics, from interesting programming to explorations of analogue film photography.