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. Theguard
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
andtry
keywords or thrown further by declaring that function withthrows
: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.