Optimizing Your Swift Code - Swift 2.0, Xcode 7 and Interface Builder - iOS 9 Swift Programming Cookbook (2015)

iOS 9 Swift Programming Cookbook (2015)

Chapter 1. Swift 2.0, Xcode 7 and Interface Builder

1.9 Optimizing Your Swift Code

Problem

You want to adopt some simple practices that can make your Swift code run much faster than before.

Solution

Use the following techniques:

1. Enable whole module optimization on your code.

2. Use value types (such as structs) instead of reference types where possible.

3. Consider using final for classes, methods, and variables that aren’t going to be overridden.

4. Use the CFAbsoluteTimeGetCurrent function to profile your app inside your code.

5. Always use Instruments to profile your code and find bottlenecks.

Discussion

Let’s have a look at an example. Let’s say that we have a Person class like so:

class Person{

let name: String

let age: Int

init(name: String, age: Int){

self.name = name

self.age = age

}

}

Now we will write a method that will generate 100,000 instances of this class, place them inside a mutable array, and then enumerate the array. We will time this operation using the CFAbsoluteTimeGetCurrent function. We’ll then be able to tell how many milliseconds this took:

func example1(){

var x = CFAbsoluteTimeGetCurrent()

var array = [Person]()

for _ in 0..<100000{

array.append(Person(name: "Foo", age: 30))

}

//go through the items as well

for n in 0..<array.count{

let _ = array[n]

}

x = (CFAbsoluteTimeGetCurrent() - x) * 1000.0

print("Took \(x) milliseconds")

}

When I ran this code, it took 41.28 milliseconds to complete; it will probably be different in your computer. Now let’s create a struct similar to the class we created before but without an initializer, because we get that for free. Then do the same that we did before and time it:

struct PersonStruct{

let name: String

let age: Int

}

func example2(){

var x = CFAbsoluteTimeGetCurrent()

var array = [PersonStruct]()

for _ in 0..<100000{

array.append(PersonStruct(name: "Foo", age: 30))

}

//go through the items as well

for n in 0..<array.count{

let _ = array[n]

}

x = (CFAbsoluteTimeGetCurrent() - x) * 1000.0

print("Took \(x) milliseconds")

}

NOTE

Don’t suffix your struct names with “Struct” like I did. This is for demo purposes only, to differentiate between the class and the struct.

When I run this code, it takes only 35.53 milliseconds. A simple optimization brought some good savings. Also notice that in the release version these times will be massively improved, because your binary will have no debug information. I have tested the same code without the debugging, and the times are more around 4 milliseconds. Also note that I am testing these on the simulator, not on a real device. The profiling will definitely report different times on a device, but the ratio should be quite the same.

Another thing that you will want to do is think about which parts of your code are final and mark them with the final keyword. This will tell the compiler that you are not intending to override those properties, classes or methods and will help Swift optimize the dispatch process. For instance, let’s say we have this class hierarchy:

class Animal{

func move(){

if "Foo".characters.count > 0{

//some code

}

}

}

class Dog : Animal{

}

And we create instances of the Dog class and then call the move function on them.

func example3(){

var x = CFAbsoluteTimeGetCurrent()

var array = [Dog]()

for n in 0..<100000{

array.append(Dog())

array[n].move()

}

x = (CFAbsoluteTimeGetCurrent() - x) * 1000.0

print("Took \(x) milliseconds")

}

When we run this, the runtime will first have to detect whether the move function is on the super class or the subclass and then call the appropriate class based on this decision. This checking takes time. If you know that the move function for instance won’t be overridden in the subclasses, mark it as final.

class AnimalOptimized{

final func move(){

if "Foo".characters.count > 0{

//some code

}

}

}

class DogOptimized : AnimalOptimized{

}

func example4(){

var x = CFAbsoluteTimeGetCurrent()

var array = [DogOptimized]()

for n in 0..<100000{

array.append(DogOptimized())

array[n].move()

}

x = (CFAbsoluteTimeGetCurrent() - x) * 1000.0

print("Took \(x) milliseconds")

}

When I run these on simulator, I get 90.26 milliseconds for the non-optimized version and 88.95 milliseconds for the optimized version. Not that bad.

I also recommend that you turn on whole module optimization for your release code. Just go to your Build Settings and under the optimization for your release builds (App Store scheme), simply choose “Fast” with Whole Module Optimization, and you are good to go.

See Also