Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Swift High Performance

You're reading from   Swift High Performance Leverage Swift and enhance your code to take your applications to the next level

Arrow left icon
Product type Paperback
Published in Nov 2015
Publisher Packt
ISBN-13 9781785282201
Length 212 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Kostiantyn Koval Kostiantyn Koval
Author Profile Icon Kostiantyn Koval
Kostiantyn Koval
Arrow right icon
View More author details
Toc

Table of Contents (10) Chapters Close

Preface 1. Exploring Swift's Power and Performance FREE CHAPTER 2. Making a Good Application Architecture in Swift 3. Testing and Identifying Slow Code with the Swift Toolkit 4. Improving Code Performance 5. Choosing the Correct Data Structure 6. Architecting Applications for High Performance 7. The Importance of Being Lazy 8. Discovering All the Underlying Swift Power Index

Swift's features and benefits

At this point, you know that you should learn Swift, and you shouldn't have any doubts. Let's take a look what makes Swift so amazing and powerful. Here is a list of a few important features that we are going to cover:

  • Clean and beautiful syntax
  • Type-safe
  • Reach types system
  • Powerful value types
  • A multiparadigm language—object-oriented, protocol-oriented, and functional
  • Generic purpose
  • Fast
  • Safe

Clean and beautiful

Powerful features and performance are important, but I think that cleanness and beauty are no less important. You write and read code everyday, and it has to be clean and beautiful so that you can enjoy it. Swift is very clean and beautiful, and the following are the main features that make it so.

No semicolons

Semicolons were created for the compiler. They help the compiler understand the source code and split it into commands and instructions. But the source code is written for people, and we should probably get rid of the compiler instructions from it:

var number = 10
number + 5

// Not recommended
var count = 1;
var age = 18; age++

There is no need for a semicolon (;) at the end of every instruction. It may seem like a very small feature, but it makes code so much nicer and easier to write and read. You can, however, put semicolons if you want. A semicolon is required when you have two instructions on the same line. There are also some exceptions when you have to use semicolons, a for loop as an example (for var i = 0; i < 10; i++), but in that context, they are used for a different purpose.

Tip

I strongly recommend not using semicolons, and avoid using more than one instruction in the same line.

Type inference

With type inference, you don't need to specify the types of variables and constants. Swift automatically detects the correct type from the context. Sometimes, however, you have to specify the type explicitly and provide type annotation. When there is no value assigned to the variable, Swift can't predict what type that variable should be:

var count = 10            //count: Int
var name = "Sara"         //name: String
var empty = name.isEmpty   //empty: Bool

// Not recommended
var count: Int = 10
var name: String = "Sara"
var empty: Bool = name.isEmpty

// When you must provide type annotation
var count: Int
var name: String

count = 10
name = "Sara"

In most cases, Swift can understand a variable's type from the value assigned to it.

Tip

Don't use type annotation if it's not required. Giving your variables descriptive names should be enough. This makes your code clean and nice to read.

Other clean code Swift features

The list of all of Swift's clean code features is very long; here are few of them: closure syntax, functions' default parameter values, functions' external parameter names, default initializers, subscripts, and operators:

  • Clean closure syntax: A closure is a standalone block of code that can be treated as a light unnamed function. It has the same functionality as a function but has a cleaner syntax. You can assign it to a variable, call it, or pass it as an argument to a function. For example, { $0 + 10 } is a closure:
    let add10 = { $0 + 10 }
    add10(5)
    
    let numbers = [1, 2, 3, 4]
    numbers.map { $0 + 10 }
    numbers.map(add10)
  • Default parameter values and external names: While declaring a function, you can define default values for parameters and give them different external names, which are used when you call that function. With default parameters, you can define one function but call it with different arguments. This reduces the need for creating unnecessary functions:
    func complexFunc (x: Int, _ y: Int = 0, extraNumber z: Int = 0, name: String = "default") -> String{
        return  "\(name): \(x) + \(y) + \(z) = \(x + y + z)"
    }
    
    complexFunc(10)
    complexFunc(10, 11)
    complexFunc(10, 11, extraNumber: 20, name: "name")
  • Default and memberwise initializers: Swift can create initializers for struct and base classes in some scenarios for you. Less code, better code:
    struct Person {
        let name: String
        let lastName: String
        let age: Int
    }
    
    Person(name: "Jon", lastName: "Bosh", age: 23)
  • Subscripts: This is a nice way of accessing the member elements of a collection. You can use any type as a key:
    let numbers = [1, 2, 3, 4]
    let num2 = numbers[2]
    
    let population = [
      "China" : 1_370_940_000,
      "Australia" : 23_830_900
    ]
    population["Australia"]

    You can also define a subscript operator for your own types or extend existing types by adding own subscript operator to them in an extension:

    // Custom subscript
    struct Stack {
      private var items: [Int]
      
      subscript (index: Int) -> Int {
        return items[index]
      }
    
      // Stack standard functions
      mutating func push(item: Int) {
        items.append(item)
      }
      
      mutating func pop() -> Int {
        return items.removeLast()
      }
    }
    
    var stack = Stack(items: [10, 2])
    stack.push(6)
    stack[2]
    stack.pop()
  • Operators: These are symbols that represent functionality, for example, the + operator. You can extend your types to support standard operators or create your own custom operators:
    let numbers = [10, 20]
    let array = [1, 2, 3]
    let res = array + numbers
    
    struct Vector {
      let x: Int
      let y: Int
    }
    
    func + (lhs: Vector, rhs: Vector) -> Vector {
      return Vector(x: lhs.x + rhs.x, y: lhs.y + rhs.y);
    }
    
    let a = Vector(x: 10, y: 5)
    let b = Vector(x: 2, y: 3)
    
    let c = a + b

    Tip

    Define your custom operators carefully. They can make code cleaner, but they can also bring much more complexity into the code and make it hard to understand.

  • guard: The guard statement is used to check whether a condition is met before continuing to execute the code. If the condition isn't met, it must exit the scope. The guard statement removes nested conditional statements and the Pyramid of Doom problem:

    Note

    Read more about the Pyramid of Doom at https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming).

    func doItGuard(x: Int?, y: Int) {
      guard let x = x else { return }
      //handle x 
      print(x)
        
      guard y > 10 else { return }
      //handle y
      print(y)
     }

A clean code summary

As you can see, Swift is very clean and nice. The best way to show how clean and beautiful Swift is is by trying to implement the same functionality in Swift and Objective-C.

Let's say we have a list of people and we need to find the people with a certain age criteria and make their names lowercase.

This is what the Swift version of this code will look like:

struct Person {
  let name: String
  let age: Int
}

let people = [
  Person(name: "Sam", age: 10),
  Person(name: "Sara", age: 24),
  Person(name: "Ola", age: 42),
  Person(name: "Jon", age: 19)
]

let kids = people.filter { person in person.age < 18 }
let names = people.map { $0.name.lowercaseString }

The following is what the Objective-C version of this code will look like:

//Person.h File
@import Foundation;

@interface Person : NSObject

@property (nonatomic) NSString *name;
@property (nonatomic) NSInteger age;

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;

@end

//Person.m File
#import "Person.h"

@implementation Person

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
  self = [super init];
  if (!self) return nil;

  _name = name;
  _age = age;

  return self;
}

@end

NSArray *people = @[
    [[Person alloc] initWithName:@"Sam" age:10],
    [[Person alloc] initWithName:@"Sara" age:24],
    [[Person alloc] initWithName:@"Ola" age:42],
    [[Person alloc] initWithName:@"Jon" age:19]
];

NSArray *kids = [people filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"age < 18"]];

NSMutableArray *names = [NSMutableArray new];
for (Person *person in people) {
  [names addObject:person.name.lowercaseString];
}

The results are quite astonishing. The Swift code has 14 lines, whereas the Objective-C code has 40 lines, with .h and .m files. Now you see the difference.

Safe

Swift is a very safe programming language, and it does a lot of security checks at compile time. The goal is to catch as many issues as possible during compiling and not when you run an application.

Swift is a type-safe programming language. If you made any mistakes with a type, such as trying to add an Int and a String or passing the wrong argument to a function, you will get an error:

let number = 10
let part = 1.5

number + part; // Error

let result = Double(number) + part

Swift doesn't do any typecasting for you; you have to do it explicitly, and this makes Swift even safer. In this example, we had to cast an Int number to the Double type before adding it.

Optionals

A very important safe type that was introduced in Swift is an optional. An optional is a way of representing the absence of a value—nil. You can't assign nil to a variable with the String type. Instead, you must declare that this variable can be nil by making it the optional String? type:

var name: String = "Sara"
name = nil //Error. You can't assign nil to a non-optional type

var maybeName: String?
maybeName = "Sara"
maybeName = nil // This is allowed now

To make a type an optional type, you must put a question mark (?) after the type, for example, Int?, String?, and Person?.

You can also declare an optional type using the Optional keyword, Optional<String>, but the shorter way with using ? is preferred:

var someName: Optional<String>

Optionals are like a box that contains some value or nothing. Before using the value, you need to unwrap it first. This technique is called unwrapping optionals, or optional binding if you assign an unwrapped value to a constant:

if let name = maybeName {
  var res = "Name - " + name
} else {
  print("No name")
}

Tip

You must always check whether an optional has a value before accessing it.

Error handling

Swift 2.0 has powerful and very simple-to-use error handling. Its syntax is very similar to the exception handling syntax in other languages, but it works in a different way. It has the throw, catch, and try keywords. Swift error handling consists of a few components, explained as follows:

  • An error object represents an error, and it must conform to the ErrorType protocol:
    enum MyErrors: ErrorType {
      case NotFound 
      case BadInstruction
    }

    Tip

    Swift enumerations fit best for representing a group of related error objects.

  • Every function that can throw an error must be declared using the throws keyword after its parameters' list:
    func dangerous(x: Int) throws
    func dangerousIncrease(x: Int) throws -> Int
  • To throw an error, use the throw keyword:
    throw MyErrors.BadInstruction
  • When you are calling a function that can throw an error, you must use the try keyword. This indicates that a function can fail and further code will not be executed:
      try dangerous(10)
  • If an error occurs, it must be caught and handled with the do and try keywords or thrown further by declaring that function with throws:
    do {
      try dangerous(10)
    }
    catch {
      print("error")
    }

Let's take a look at a code example that shows how to work with exceptions in Swift:

enum Error: ErrorType {
  case NotNumber(String)
  case Empty
}

func increase(x: String) throws -> String {
  if x.isEmpty {
    throw Error.Empty
  }
  
  guard let num = Int(x) else {
    throw Error.NotNumber(x)
  }
  
  return String(num + 1)
}

do {
  try increase("10")
  try increase("Hi")
}
catch Error.Empty {
  print("Empty")
}
catch Error.NotNumber (let string) {
  print("\"\(string)\" is not a number")
}
catch {
  print(error)
}

There are many other safety features in Swift:

  • Memory safety ensures that values are initialized before use.
  • Two-phase initialization process with security checks
  • Required method overriding and many others

Rich type system

Swift has the following powerful types:

  • Structures are flexible building blocks that can hold data and methods to manipulate that data. Structures are very similar to classes but they are value type:
    struct Person {
      let name: String
      let lastName: String
    
      func fullName() -> String {
        return name + " " + lastName
      }
    }
    
    let sara = Person(name: "Sara", lastName: "Johan")
    sara.fullName()
  • Tuples are a way of grouping multiple values into one type. Values inside a tuple can have different types. Tuples are very useful for returning multiple values from a function. You can access values inside a tuple by either index or name if the tuple has named elements; or you can assign each item in the tuple to a constant or a variable:
    let numbers = (1, 5.5)
    numbers.0
    numbers.1
    
    let result: (code: Int, message: String) = (404, "Not fount")
    result.code
    result.message
    
    let (code ,message) = (404, "Not fount")
  • Range represents a range of numbers from x to y. There are also two range operators that help create ranges: closed range operator and half-open range operator:
    let range = Range(start: 0, end: 100)
    let ten = 1...10 //Closed range, include last value 10
    let nine = 0..<10 //half-open, not include 10
  • Enumeration represents a group of common related values. An enumeration's member can be empty, have a raw value, or have an associated value of any type. Enumerations are first-class types; they can have methods, computed properties, initializer, and other features. They are great for type-safe coding:
    enum Action: String {
      case TakePhoto
      case SendEmail
      case Delete
    }
    
    let sendEmail = Action.SendEmail
    sendEmail.rawValue //"SendEmail"
    
    let delete = Action(rawValue: "Delete")

Powerful value types

There are two very powerful value types in Swift: struct and enum. Almost all types in the Swift standard library are implemented as immutable value types using struct or enum, for example, Range, String, Array, Int, Dictionary, Optionals, and others.

Value types have four big advantages over reference types, they are:

  • Immutable
  • Thread safe
  • Single owned
  • Allocated on the stack memory

Value types are immutable and only have a single owner. The value data is copied on assignment and when passing it as an argument to a function:

var str = "Hello"
var str2 = str

str += " :)"

Note

Swift is smart enough to perform value copying only if the value is mutated. Value copying doesn't happen on an assignment, that is str2 = str, but on value mutation, that is str += ":)". If you remove that line of code, str and str2 would share the same immutable data.

A multiparadigm language

Swift is a multiparadigm programming language. It supports many different programming styles, such as object-oriented, protocol-oriented, functional, generic, block-structured, imperative, and declarative programming. Let's take a look at a few of them in more detail here.

Object oriented

Swift supports the object-oriented programming style. It has classes with the single inheritance model, the ability to conform to protocols, access control, nested types and initializers, properties with observers, and other features of OOP.

Protocol oriented

The concept of protocols and protocol-oriented programming is not new, but Swift protocols have some powerful features that make them special. The general idea of protocol-oriented programming is to use protocols instead of types. In this way, we can create a very flexible system with weak binding to concrete types.

In Swift, you can extend protocols and provide a method's default implementation:

extension CollectionType {

  func findFirst (find: (Self.Generator.Element) -> Bool) -> Self.Generator.Element? {
    
    for x in self { 
      if find(x) {
        return x
      }
    }
    return nil
  }
}

Now, every type that implements CollectionType has a findFirst method:

let a = [1, 200, 400]
let r = a.findFirst { $0  > 100 }

One big advantage of using protocol-oriented programming is that we can add methods to related types and use the dot (.) syntax for method chaining instead of using free functions and passing arguments:

let ar = [1, 200, 400]

//Old way
map(filter(map(ar) { $0 * 2 }) { $0 > 50 }) { $0 + 10 } 

//New way
ar.map{ $0 * 2 } .filter{ $0 > 50 } .map{ $0 + 10 }

Functional

Swift also supports the functional programming style. In functional languages, a function is a type and it is treated in the same way as other types, such as Int; also, it is called a first class function. Functions can be assigned to a variable and passed as an argument to other functions. This really helps to decouple your code and makes it more reusable.

A great example is a filter function of an array. It takes a function that performs the actual filtering logic, and it gives us so much flexibility:

// Array filter function from Swift standard library
func filter(includeElement: (T) -> Bool) -> [T]

let numbers = [1, 2, 4]

func isEven (x: Int) -> Bool {
    return x % 2 == 0
}
let res = numbers.filter(isEven)

Generic purpose

Swift has a very powerful feature called generics. Generics allow you to write generic code without mentioning a specific type that it should work with. Generics are very useful for building algorithms, reusable code, and frameworks. The best way to explain generics is by showing an example. Let's create a minimum function that will return a smaller value:

func minimum(x: Int, _ y: Int) -> Int {
  return (x < y) ? x : y
}

minimum(10, 11)
minimum(11,5, 14.3) // error

This function has a limitation; it will work only with integers. However, the logic of getting a smaller value is the same for all types—compare them and return the smaller value. This is very generic code.

Let's make our minimum function generic and work with different types:

func minimum <T : Comparable>(x: T, _ y: T) -> T {
  return (x < y) ? x : y
}

minimum (10, 11)
minimum (10.5, 1.4)
minimum ("A", "ABC")

Tip

The Swift standard library has already implemented a generic min function. Use that instead.

Fast

Swift is designed to be fast and have high performance, and this is achieved with the following techniques:

  • Compile-time method binding
  • Strong typing and compile time optimization
  • Memory layout optimization

Later, we will cover in more detail how Swift uses these techniques to improve performance.

You have been reading a chapter from
Swift High Performance
Published in: Nov 2015
Publisher: Packt
ISBN-13: 9781785282201
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime