Swifter - 100 must know tips for Swift
Swifter - 100 must know tips for Swift
Wei Wang
Buy on Leanpub

Introduction

Why do I should read this book?

Quite a lot of learners of Swift - whether those who are totally new to Cocoa/Cocoa Touch, or used to write in Objective-C - are struggling in the same situation: how to improve thier professional skill after getting started with Swift. Maybe it happens to your situation as well. When you finished the last page of Apple’s Swift tutorial, thinking you had mastered the new language, then created an Xcode project with Swift, and stopped at the very first line of code. You have to recall when to use Optional and when not to. You are not sure of how to express these familiar APIs in the new language. You are always getting trouble with compiling the code, without any clue for fixing them.

Don’t worry, that all quite normal. The Apple’s tutorial is written for showing the syntax to you. If you want to make Swift a powerful weapon in your daily life, you have to learn it deeper and use it more. The target of this book is introducing some innovative points for you, and improving your practice skill. These parts of knowledge are necessary for engineers who are using or wishing to use Swift as their next programming language.

What is contained in this book?

It is a collection of knowledge point and tips for Swift. I myself attended WWDC 14, and saw the birth of Swift with my own eyes. From the very first minute, I am learning Swift. By now, I concluded about 100 tips for this language, and divided them into 100 chapters, from the very basic ones to some high level ones. Every chapter has unique content which should be understood by a senior developer.

This book is particularly suitable to be used as a reference and supplement of Swift official documentation. It would also become a fancy hand-book for developers. For the detail of what are contained in the book, you can refer to the Contents page of the book.

What is not contained in this book?

This should not be your FIRST book for learning Swift, and this book is no longer a tutorial for you to develop a simple calculator or note app. The main purpose of this book is clear - to explore those ignored details of Swift. Although we will not discuss the whole language in a systematic way, these points are utilized widely in developing. Based on the purpose, the chapters of the book are organized in a loose structure.

Generally speaking, if you are just looking for a beginner book of Swift, you may probably choose this one. You can first read Apple’s documentation on Swift, then have a look at this book later on.

How to read this book

100 is not a small number for tips. Fortunately, every part of this book is not so tightly with each other, that allows you to just open the book and pick any chapter you like. Although I recommend to follow the order - because I paid specially attention not to refer the hard part in the earlier chapters, it is not a must-done rule. And there are links to the referred chapters as well, you can jump through the book easily, and review the related chapters as you wish. If you are not interested in some chapters, just skip them first. You can always pick these ones would help you mostly go back to the skipped chapters later.

I suggest practising the code in Xcode while reading. It could be a help to understand the intension of these sample code. Every sample code is not long, but prepared carefully. I hope you can “talk” to me by writing these codes.

Code Sample

There are code samples in most chapters, mainly in Swift, and some in Objective-C as a reference. All code should be able to run in Swift 1.1 (which means Xcode 6.1). Of course, the change is happening in Swift very rapidly now. Some code might be needed some modification to be compiled and run correctly. If you find it, please open an issue in the repository of this book, I will fix them as soon as possible.

If not specially pointed out, these codes could be executed in both Playground and real project, and should share the same result. But there is also the situation of the code could be only tested in Playground OR the real project. This is always caused by the limitation of the platform, and I will mention it in the chapter.

Errata and Feedback

Swift is of great speed development and this book is under Swift 1.1 currently. As some new features introduced into Swift, there might be outdated content in the book. Despite the fact that I will improve this book, it might take some time after the upgrading of the language itself.

If you find any problem in reading this book, it would greatly help if you can report it in the issue page. I will confirm these feedback and fix the issues if necessary. Every update is writing in the release note. You can also find the same list indicating what changed in the wiki page of the repository.

Aboud the author

Wei Wang (onevcat) is an iOS developer from China. He got his master degree in Tsinghua University, which is one of the best universities in China. When he was an undergrad he started to develop iOS app. Now he is a senior engineer with Cocoa and Objective-C experience. He is writing a Chinese blog about iOS development in OneV’s Den with thousands of subscribers. He loves to contribute to open source community and is the author of a famous Xcode plugin VVDocumenter as well.

Now Wei Wang is working in Japan for a mobile communication company. If you want to know more about him, please visit his personal page and follow him on Twitter.

Selector

@selector is keyword in Objective-C. It could convert a method to a SEL type, which turns out behaving as a “function pointer”. In the old Objective-C’s age, selector is widely used, from setting the target-action to introspecting. In Objective-C, the way of generating a selector would be:

-(void) callMe {
    //...
}

-(void) callMeWithParam:(id)obj {
    //...
}

SEL someMethod = @selector(callMe);
SEL anotherMethod = @selector(callMeWithParam:);

// Or we can use NSSelectorFromString as well
// SEL someMethod = NSSelectorFromString(@"callMe");
// SEL anotherMethod = NSSelectorFromString(@"callMeWithParam:");

For writing less code, @selector is widely used. But if we need to decide which method to call at runtime, NSSelectorFromString would be preferred, so we could generate the method string dynamically and send the message with the name.

A bad news for selector lovers, there is no @selector keyword anymore in Swift. Now if we aim at creating a selector, the only choice is from string. The original SEL type is also replaced by a Selector struct, which contains an init method. The code above in Swift should be:

func callMe() {
    //...
}

func callMeWithParam(obj: AnyObject!) {
    //...
}

let someMethod = Selector("callMe")
let anotherMethod = Selector("callMeWithParam:")

Same as Objective-C, you have to add the colon (:) following callMeWithParam to construct the full method name. Method with multiple parameters is similar, like this:

func turnByAngle(theAngle: Int, speed: Float) {
    //...
}

let method = Selector("turnByAngle:speed:")

Besides of that, since Selector type conforms the StringLiteralConvertible protocol, we could even use a string to assign a selector, without using its init method explicitly. It is semantic in some situation, for example, setting a call back method when adding a notification observer:

NSNotificationCenter.defaultCenter().addObserver(self,
        selector: "callMe", name: "CallMeNotification", object: nil)

We should pay special attention on the fact of selector being a concept of Objective-C runtime. If the method of a selector is only exposed in Swift, but not Objective-C (or in other words, it is a private method of Swift), you might get an “unrecognized selector” exception when sending method to this selector:

One solution would be adding @objc keyword before private, so the runtime could get to know what you mean by the selector.

@objc private func callMe() {
    //...
}

NSTimer.scheduledTimerWithTimeInterval(1, target: self, 
             selector:"callMe", userInfo: nil, repeats: true)

Lastly, if the first parameter of a method has an external name, there is a convention to remember when creating a selector. We should add a with between the method name and the initial external name. For example:

func aMethod(external paramName: AnyObject!) { ... }

To get the Selector of this method, we should do like this:

let s = Selector("aMethodWithExternal:")

Sequence

The for...in syntax can be used in the types which conformed SequenceType. And those types should conform GeneratorType first. For example, a reversed generator and the corresponding sequence could be written as:

// 1
class ReverseGenerator: GeneratorType {
    typealias Element = Int
    
    var counter: Element
    init<T>(array: [T]) {
        self.counter = array.count - 1
    }
    
    init(start: Int) {
        self.counter = start
    }
    
    func next() -> Element? {
        return self.counter < 0 ? nil : counter--
    }
}

// 2
struct ReverseSequence<T>: SequenceType {
    var array: [T]
    
    init (array: [T]) {
        self.array = array
    }
    
    typealias Generator = ReverseGenerator
    func generate() -> Generator {
        return ReverseGenerator(array: array)
    }
}

let arr = [0,1,2,3,4]

// 3
for i in ReverseSequence(array: arr) {
    println("Index \(i) is \(arr[i])")
}
  1. First we use a type conforms GeneratorType protocol. A typealias Element should be specified in GeneratorType, and then we should implement a method named next() to return an Element?.
  2. Now we can define the SequenceType. Similar to the GeneratorType,but specifying a Generator typealias instead. At last, we implement a generate() method to return a Generator?.
  3. Finally we can use for...in to access the SequenceType in a loop.

The output is

Index 4 is 4
Index 3 is 3
Index 2 is 2
Index 1 is 1
Index 0 is 0

Let’s expand the for...in syntax to see what happens under the hood. The equivalent is:

var g = array.generate()
while let obj = g.next() {
    println(obj)
}

By the way, you can get to be able to use map, filter and reduce on it for free if you make it conform to SequenceType, since all of them have a SequenceType version:

@autoclosure and ??

Apple opened a blog to write about and promote Swift, which is really rare in Apple’s history. And there is a post there mentioned the @autoclosure keyword.

@autoclosure is an amazing creation of Apple, it “seems” more likely a hack for this language. The thing @autoclosure does is encapsulating a statement into a closure, automatically. This slick up your code almost all the time, turning it understandable and neat.

Consider if we have a method, which accepting a closure as parameter. When the closure is evaluated as true, print a string:

func logIfTrue(predicate: () -> Bool) {
    if predicate() {
        println("True")
    }
}

And we have to write this code to call it:

logIfTrue({return 2 > 1})

It is annoying: What is that return and there are () and {} pairs which make the code difficult to understand.

Of course, we could simplify the closure in this situation. Here it would be no problem to omit return, making the code above to:

logIfTrue({2 > 1})

One step further, there is a syntax called trailing closure. We can omit the parentheses as well:

logIfTrue{2 > 1}

Fairly well, but not enough. If a programmer just switchs to Swift and has no idea about trailing closure, he may be confused with the brace. Now, if we use @autoclosure, we can make the world better for everyone. Just adding @autoclosure in front of the parameter type:

func logIfTrue(@autoclosure predicate: () -> Bool) {
    if predicate() {
        println("True")
    }
}

Now, we can write this:

logIfTrue(2 > 1)

to call the logIfTrue method. Swift will transform the statement 2 > 1 into a () -> Bool closure behind the scene. So we can have a good and simple expression.

Another important use case is ?? operator. ?? can be used in nil condition check. If the left-hand of this operator is a non-nil Optional, the unwrapped value would be returned, otherwise, the right-hand default value will be used. Just a simple example:

var level : Int?
var startLevel = 1

var currentLevel = level ?? startLevel

Here we do not set level when declare it, it will be a nil. In the currentLevel statement, startLevel is assigned to currentLevel, since there is no value in level. If you are a curious cat, you may already click into the definition of ??, which contains two versions:

func ??<T>(optional: T?, @autoclosure defaultValue: () -> T?) -> T?
func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T

In the example, we are using the latter one. Although it seems we are passing startLevel as a simple Int, it is a () -> Int in fact, and be wrapped automatically by the @autoclosure feature. Think about how ugly it will be if there is no @autoclosure! With the hint of this method signature, we can try to implement this operator ourselves, like this:

func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T {
    switch optional {
        case .Some(let value):
            return value
        case .None:
            return defaultValue()
        }
}

Think deeper, and you may have a question about this operator: Why does not this operator just accepting a T as right-hand parameter? Is it not simpler by just using T instead of @autoclosure? That is the greatest part of @autoclosure. If we use T, that means we need to prepare everything ready, both the left and right-hand parameters, before passing them to the ?? operator. However, once the left-hand value optional is not nil, the unwrapped value will be returned immediately. In most case, it will not be a problem, but consider the situation that we are using a very complex algorithm to calculate the defaultValue. We have to prepare it everytime using ?? operator, but in fact there is a possibility that we will not use it at all. What a waste! This could be avoided in fact. The key is delaying the calculating for defaultValue, making it following the nil check. The answer is @autoclosure it, so it will not be executed until optional falls into the .None case.

With @autoclosure, we are not only bypassed the conditional checking and explicitly conversion, but also get a benefit in performance. But there is also limitation when using @autoclosure. It cannot support method with parameter, in other words, you can not mark @autoclosure except for () -> T. Another trap is the caller of your code might not notice you want to accept an autoclosure parameter. When writing methods with @autoclosure, it is better to keep it simple and clear. If the parameter might create an ambiguity or be difficult to understand, please make the choice to turn to a complete closure, so everyone could know what happens in the code.

Optional Chaining

By using optional chaining, we can get rid of checking nil optional again and again, but there are also some pitfalls to avoid.

The evaluation of optional chaining might early return a nil. It means everything we get from an optional chaining will be optional. Consider the code below:

class Toy {
    let name: String
    init(name: String) {
        self.name = name
    }
}

class Pet {
    var toy: Toy?
}

class Child {
    var pet: Pet?
}

If there is a boy called Tom and when we need to know the toy’s name which Tom’s pet owns, we can get it with the optional chaining:

let toyName = tom.pet?.toy?.name

Although we are accessing the name property in Toy, which is defined as a String type instead of a String?, we actually get a String? type constant toyName. This happens because in the optional chaining, every ?. would be a nil, so nil must be an option for the final evaluated value.

We could apply an optional binding in the result and get the real value if exists:

if let toyName = tom.pet?.toy?.name {
    // Great, Tom has a pet, and the pet happens has a toy.
}

Quite clear? Yes and no. If we only use optional chaining we can always pay attention that the result is an optional, but when we combine it with some other features of Swift, things will get complicated soon. Say we write an extension of Toy:

extension Toy {
    func play() {
        //...
    }
}

Yeah, now the pet can play the toy:

tom.pet?.toy?.play()

And of course, Bob, Leo and Matt also have their pet. If we abstract the method into a closure, and passing the Child as a parameter, we would write:

Simple code, isn’t it? But it won’t compile at all!

The problem is the type of calling play(). We do not specify any return type for play(), which means the method will return a () (or we can also write it as Void, they are the same). Nevertheless, as mentioned above, it is called in an optional chaining. So the result would be an Optional value. Instead of getting the original Void, we will actually get a ()?.

let playClosure = {(child: Child) -> ()? in child.pet?.toy?.play()}

Or we should change it to a clearer way, Void?. Despite the fact that it seems a little weird, it is the truth. When using it, we could check whether the method is called successfully or not by unwrapping the result with optional binding.

if let result: () = playClosure(tom) {
    println("Happy~")
} else {
    println("No toy :(")
}

Parameter Type of Function

When declaring a function in Swift, we will add any modifier in front of parameters normally:

func incrementor(variable: Int) -> Int {
    return variable + 1
}

This function accepts an Int input, adds a 1, then returns a new Int value 1 greater than the input one. Nothing special, it is a simple “+1” adder.

If you know the increment operator ++, you may remember this operator means “plus 1 and assign back”. So is it OK to write this code instead of above?

Oops..compile error. The similar code would be compiled in C and Objective-C, so why not in Swift? Swift is a language “hates” mutability. The default input parameter is a constant value in fact, which means an implicit let is used here. The equivalent code of above would be

Of course, a let constant cannot be assigned again. To make the snippet gets compiled, we can modify the let to var. It indicates the input is not a constant anymore, so it can be re-assigned in the method:

func incrementor(var variable: Int) -> Int {
    return ++variable
}

Cheers, the “+1” adder works again:

var luckyNumber = 7
let newNumber = incrementor(luckyNumber)
// newNumber = 8

println(luckyNumber)
// luckyNumber is still 7

As commented above, since we changed the parameter to var, the method is returning the correct value for us. At the same time, luckyNumber keeps the original value. That means the var modifier only works in the method, without effecting value outside the method. Sometimes, we wish change the input value just in place in the method. inout is for it:

func incrementor(inout variable: Int) {
    ++variable
}

As a side effect, since the variable is increased itself, we do not have to return it anymore. The calling is changed also, we need to add a &:

var luckyNumber = 7
incrementor(&luckyNumber)

println(luckyNumber)
// luckyNumber = 8

There is a limitation when passing parameters with modifier. All parameters in different level of methods should keep the same. For example, if we want to expand the code above to a general “+N” adder, one way is by nested function like this:

func makeIncrementor(addNumber: Int) -> ((inout Int) -> ()) {
    func incrementor(inout variable: Int) -> () {
        variable += addNumber;
    }
    return incrementor;
}

The outer makeIncrementor should mark inout in the return value explicitly as well to match the inner definition, or it will not compile at all.