Home Programming Hands-On Design Patterns with Swift

Hands-On Design Patterns with Swift

By Florent Vilmart , Giordano Scalzo , Sergio De Simone
books-svg-icon Book
eBook $39.99 $27.98
Print $48.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $39.99 $27.98
Print $48.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Refreshing the Basics
About this book
Swift keeps gaining traction not only amongst Apple developers but also as a server-side language. This book demonstrates how to apply design patterns and best practices in real-life situations, whether that's for new or already existing projects. You’ll begin with a quick refresher on Swift, the compiler, the standard library, and the foundation, followed by the Cocoa design patterns – the ones at the core of many cocoa libraries – to follow up with the creational, structural, and behavioral patterns as defined by the GoF. You'll get acquainted with application architecture, as well as the most popular architectural design patterns, such as MVC and MVVM, and learn to use them in the context of Swift. In addition, you’ll walk through dependency injection and functional reactive programming. Special emphasis will be given to techniques to handle concurrency, including callbacks, futures and promises, and reactive programming. These techniques will help you adopt a test-driven approach to your workflow in order to use Swift Package Manager and integrate the framework into the original code base, along with Unit and UI testing. By the end of the book, you'll be able to build applications that are scalable, faster, and easier to maintain.
Publication date:
December 2018
Publisher
Packt
Pages
414
ISBN
9781789135565

 

Chapter 1. Refreshing the Basics

In order to properly kick-start our journey through Swift's best practices and design patterns, I believe it's important that we take some time to go back to the basics. It's important to always keep your foundation strong; the more we advance through this book, the more we'll rely on those concepts.

I'll assume that you have a proper understanding of object-oriented programming (OPP) fundamentals, classes, inheritance, composition, and other techniques, as well as a fundamental understanding of the differences between value and reference types. If you're rusty on these concepts, you shouldn't worry too much, as we'll cover them shortly.

This chapter will dive deeply into the Swift language. What is a struct, and what is a class? What are their differences? Should you use an enum or an OptionSet? All of these questions will be answered in this chapter. We'll go back to the basics of classes and inheritance, and we'll discover the power of value types and immutability. We'll look it functions, closures, and currying. If you're unfamiliar with these constructs, or if you just want to get a refresher, you should tag along as we go back to the basics. These basics are essential to the Swift language, and are required to successfully apply efficient design patterns and best practices.

In this first chapter, we'll take the time to go back to the basics by covering the following topics:

  • Classes and structs: what they are, and how they behave
  • Exploring enums and their capabilities and extensibility
  • Getting functional with closures and functions
  • Introducing protocols and scratching the surface of extending protocols
  • Concluding with other useful language constructs, such as type aliases, tuples, and generics
 

Classes and structs


Let's start with a quick refresher on classes and structures. Both of them help to encapsulate functionality by defining methods and properties. While they share the same semantics, they differ in many ways. In this section, we'll quickly refresh you on the differences between classes and structs, and we will show you a simple refactoring from classes to structs.

Classes

Let's start with an example of a simple class that represents a Point in an x, y coordinate system (Cartesian). Consider the following:

class Point {
    var x: Double
    var y: Double

    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
}

Now, let's define a simple translate function that will mutate the x and y properties of the point objects by adding dx and dy to x and y, respectively:

func translate(point : Point, dx : Double, dy : Double) {
 point.x += dx
 point.y += dy
}

Now, we can create a point instance with, for example, an initial value of 0.0, and translate it to the position 1.0:

let point = Point(x: 0.0, y: 0.0)
translate(point: point, dx: 1.0, dy: 1.0)
point.x == 1.0
point.y == 1.0

Because classes follow reference semantics, only a reference to the point object is passed to the translate function; x and y are defined as var, and all of this code is valid.

Struct

Now, let's try to port our Point class into a struct. Consider the following:

struct Point {
    var x: Double
    var y: Double
}

We have defined a simple struct; as you should notice, there's no need to add a constructor, as the compiler will synthesize it for us:

let point = Point(x: 0.0, y: 0.0)
translate(point: point, dx: 1.0, dy: 1.0)

If we keep the original implementation, our program won't compile. Point is a value type now, and it's forbidden to mutate a value inside of a function! We need to add the inout keyword to indicate that this function will mutate the contents of the value that is passed. When the function returns, the value will be assigned back to the original variable.

With those changes complete, we also need to change our call to indicate that our point variable can be modified by our translate function with the & (ampersand) character. We also need to mark our point as var; otherwise, the inout function cannot modify its contents:

func translate(point: inout Point, dx : Double, dy : Double) {
    point.x += dx
    point.y += dy
}

var point = Point(x: 0.0, y: 0.0)
translate(&point, dx: 1.0, dy: 1.0)
point.x == 1.0 // true
point.y == 1.0 // true

We've successfully ported this function, but we can do better.

With structs, you will often see that this pattern is cumbersome. We may want the translate function to return a mutated copy of the value we passed in, as follows:

func translate(point: Point, dx : Double, dy : Double) -> Point {
    var point = point
    translate(point: &point, dx : dx, dy : dy)
    return point
}

We'll be able to use the previously defined function with the following code:

let point = Point(x: 0.0, y: 0.0)
let translatedPoint = translate(point, dx: 1.0, dy: 1.0)
point.x == 0.0
point.y == 0.0
translatedPoint.x == 1.0
translatedPoint.y == 1.0

With this new implementation, we're not mutating the value anymore, but the translate function is always returning a new Point value. This has many benefits, including the ability to chain such calls together. Let's add a method to our Point struct:

extension Point {
    func translating(dx: Double, dy: Double) -> Point {
        return translate(point: self, dx: dx, dy: dy)
    }
}

Note

You don't need to declare this new method in your struct, but you can declare it anywhere in your program. 

Using our newly crafted extension, we can easily create new Point values and translate them:

let point = Point(x: 0.0, y: 0.0)
    .translating(dx : 5.0, dy : 2.0)
    .translating(dx : 2.0, dy : 3.0)
point.x == 7.0
point.y == 5.0
 

Enums


Enums are one of the basic constructs that the Swift language offers. At the same level as classes, structs, and functions, they are used to represent values that can only have a finite amount of states.

Take the Optional enum, for example; it is represented by an enum perfectly. It represents a value that can have two, and only two, states, represented by the two members of the Optional enum. It can either be initialized to .none or filled with a value, .wrapped(value).

Enums are incredibly powerful in Swift. From very simple cases to generics, they are among the most powerful tools that we have for writing our programs.

Simple enums

Let's say you're building a smart light remote control; you can easily represent the state of this light with the following enum:

enum State {
    case on
    case off
}

let anOnLight = State.on

This is a very simple example, and we could have used a Boolean value, but with the enum, we set ourselves up for expansion.

Adding methods

Now, we may want to add a method to this State enumeration. After all, it's very common to just toggle the switch on and off without thinking:

extension State {
    mutating func toggle() {
        self = self == .off ? .on : .off
    }
}

var state: State = .on
state.toggle()
state == .off // true

As in the previous section, we can just extend the State enum to add the toggle functionality. Enums follow value semantics; therefore, we have to mark the toggle method as mutating.

Associating values

Enums can also contain associated values. In our scenario, we can leverage this to represent a dimmer. A dimmer changes the intensity of the light, so we can represent it with a third member-the dimmed member:

enum State: Equatable {
    case on
    case off
    case dimmed(value: Double)
}

You may have noticed that we needed to add the Equatable conformance. This is required, as otherwise, the compiler can't synthesize equality anymore without our hint. This implementation works, but we lack a few things. First, not all Double values are valid; we'd probably like to keep these in a reasonable span (between 0 and 1, for example). But perhaps not all of our lights support such values between 0 and 1. Others may want to support between 0 and a 100 or integers between 0 and 255.

Generic enums

In the following example, we will build a fully generic light:

enum State<T>: Equatable where T: Equatable {
    case on
    case off
    case dimmed(T)
}

struct Bits8Dimming: Equatable {
    let value: Int
    init(_ value: Int) {
        assert(value > 0 && value < 256)
        self.value = value
    }
}

struct ZeroOneDimming: Equatable {
    let value: Double
    init(_ value: Double) {
        assert(value > 0 && value < 1)
        self.value = value
    }
}

let nostalgiaState: State<Bits8Dimming> = .dimmed(.init(10))
let otherState: State<ZeroOneDimming> = .dimmed(.init(0.4))

The dim type is now specified as a part of the State type. This gives us a lot of flexibility, as well as validation. Wrapping the value into a small struct adds very little overhead in terms of performance, and allows us to ensure that the values are sane before being set into our enum

Raw type enums

A raw type is a base type for all enumeration members; in our example, we can hardcode presets for our dimming, as follows:

enum LightLevel: String {
    case quarter
    case half
    case threequarters
}

let state: State<LightLevel> = .dimmed(.half)

Thanks to the generic implementation and the fact that String is equatable, we can use this raw value in our dimmed state.

With the LightLevel enum, which has a raw type of String, the compiler will use the member name as the underlying raw value:

LightLevel.half.rawValue == “half” // == true

You can override these by specifying them, as follows:

enum LightLevel: String {
    case quarter = “1/4”
    case half = “1/2”
    case threequarters = “3/4”
}

When using Int as a raw type, the underlying raw values will follow the order of the cases:

enum Level: Int {
    case base // == 0
    case more // == 1
    case high = 100
    case higher // == 101
}

Switching the state of light

With our final case, let's look at how to interpret the current state of the light:

switch state {
case .on:
    doSomething()
case .off:
    doSomething()
case .dimmed(let value):
    switch value {
    case .quarter:
        doSomething()
    case .half:
        doSomething()
    case .threeQuarters:
        doSomething()
    }
}

The switch statement in Swift is very different from the one in Objective-C. First, the cases do not fall through each other, so there's no need to add the break statement after each case.

If you want multiple cases to be handled with the same code, you can use the following strategy:

switch state {
case .on, .off:
    doSomething()
default:
    break
}

Falling through is somehow not encouraged in Swift, so always try to adapt your code in order not to leverage this. If you can't avoid it, the following code shows how it should be implemented:

switch state {
case .off:
    doSomethingOff()
    fallthrough
case .on:
    doSomething()
default:
    break
}

If state is off, both doSomethingOff and doSomething will be called. If state is on, only doSomething will be called.

 

Closures, functions, and currying


Closures are blocks of code that can be executed later, and functions are a special case of closures. Functions and closures can be passed around in your code, returned by other functions or closures. You can store a closure or a function in a variable, and execute them later:

let runMe = { () -> Int in
    print(“run”)
    return 0
}
runMe()

The preceding code is equivalent to the following:

func runMe() -> Int {
    print(“run”)
    return 0
}
runMe()

Closures and functions are almost always interchangeable, except when it comes to class or struct members:

class MyClass{
var running = false
lazyvar runWithClosure: () -> Void = {
self.running = true
}

func runWithFunction() {
self.running = true
}
}

While both implementations are somewhat equivalent, we rarely want this function to be overridable at runtime. The closure can't reference self inside of it, unless marked lazy. Marking it lazy forces the implementation to be var, which, in turn, doesn't reflect what we want to express. In practice, we never declare instance methods as closures.

Currying

Functions and closures don't have to be defined at the top level. This can be unintuitive, when coming from languages such as Objective-C and Java. Swift, like JavaScript, lets you define functions and closures anywhere in your code. Functions can also return functions. This mechanism is known as currying.

Imagine that you want to create a logger method that will print a single argument, but it will always pretend to be a string to find it easily in your logs.

Let's start with the following basic implementation:

private let PREFIX = ‘MyPrefix'

private func log(_ value: String) {
    print(PREFIX + “ “ + value)
}

class MyClass {
    func doSomething() {
        log(“before”)
        /* complex code */
        log(“after”)
    }
}

While this works properly in the scope of a simple class, if you need to reuse the log method or change the internal implementation, this will lead to a lot of duplication.

You can use currying to overcome that issue, as follows:

func logger(prefix: String) -> (String) ->  Void {
    func log(value: String) {
        print(prefix + “ “ + value)
    }
    return log
}

let log = logger(prefix: “MyClass”)
log(“before”)
// do something
log(“after”)

// console:
MyClass before
MyClass after

Using closures as callbacks

Functions and closures can capture the current scope, which means all of the declared variables outside of the function or closure definition, such as local variables or self. In the case of self, you can inadvertently extended the lifetime of your objects and leak memory:

class MyClass {
    var running = false
    func run() {
        running = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
            self.running = false
        }
    }
}

var instance: MyClass? = MyClass()
instance?.run()
instance = nil

Can you spot the potential issue in this code?

Depending on the use case, you may want instance to be destroyed when it is not referenced by any owner. In our case, we'll probably cause a memory leak, as the dispatch block is referencing self without any memory management qualifier.

Using weak and unowned

Swift provides us with two keywords that indicate how we want to extend the lifetime of an object in a closure. While both prevent creating retain cycles, they are fundamentally different.

Using weak will wrap the captured value inside of an optional, indicating that the instance may have been deallocated before the closure was executed:

class MyClass {
    var running = false
    func run() {
        running = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
            self?.running = false
        }
    }
}

var instance: MyClass? = MyClass()
instance?.run()
instance = nil

In this execution, instance will immediately be deallocated when set to nil.

Using unowned indicates that the variable won't be owned by the block. Another mechanism should be responsible for ensuring that the lifetime of the captured object is properly extended until the block is executed:

class MyClass {
    var running = false
    func run() {
        running = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [unowned self] in
            self.running = false
        }
    }
}

var instance: MyClass? = MyClass()
instance?.run()
instance = nil

In this case, your program will crash when the block is executing, because the self variable will be deallocated upon the execution of the block:

Fatal error: Attempted to read an unowned reference but object 0x7f80bc75a4e0 was already deallocated
 

Protocols


The following is from Apple's Swift Programming Language book:

"A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol."  

– Apple Inc., The Swift Programming Language (Swift 3.0.1), iBooks

Protocol-oriented programming is a vast topic that also deserves coverage. It is the subject of many discussions, and I won't dive into it in depth. However, let's go over the basic concepts, as they will be useful for understanding some concepts that will be explained later in this book.

Declaring a protocol

You declare protocols using the protocol keyword, as follows:

protocol Toggling {
    mutating func toggle()
}

Now that this protocol has been declared, any time that you declare a type conforming to Toggling, you'll be required to implement a mutating toggle() function.

You can use protocols in your type declarations, method declarations, or variable declarations. While it is technically possible to use protocols as interfaces for your objects or structs, that is usually not how they are used in Swift. Often, you will find yourself conforming to protocols when declaring your custom types or later in your code base, part of extending your existing type to bring additional functionality to it. 

Conforming to a protocol

We have just declared this new toggling protocol. If we go back to the previous section about enums, you may remember that the State enum had a toggle() method. We can now declare that our enumState, conforms to Toggling. As mentioned previously, we have many ways to declare our conformance. 

Conformance at declaration

The first method to declare a conformance is to do it at the top level, when you declare your custom type. You'll notice that the raw representation comes first, then the protocol conformance:

enum State: Int, Toggling {
    case off = 0
    case on

    mutating func toggle() {
        self = self == .on ? .off : .on
    }
}

var state: State = .on
state.toggle()
assert(state == .off)

Conformance in an extension

The second way to declare a conformance is to add the conformance to an extension. The main benefit is that you can add functionalities, in the form of extensions, to existing types. The other main benefit of declaring a conformance inside of an extension is that you can scope this conformance to a particular file or module, with the private modifier.

For example, suppose that we want to add the toggle method to the Bool type, but only for the current file or your framework. You may not want it to leak outside, as the implementation may conflict with another one:

internal extensionBool: Toggling {
mutatingfunc toggle() {
self = !self
}
}

var isReady = false
isReady.toggle()
assert(isReady)

Protocol extensions

With protocol extensions, it is possible to provide an implementation for the required methods, without letting the conforming types provide that implementation.

We have updated the Toggling protocol with an additional required member: isActive. With the protocol extension, we can declare a default implementation for our types, Bool and State. We can also provide a default implementation for any other type that would choose to conform to the Toggling protocol:

protocol Toggling {
    mutating func toggle()
    var isActive: Bool { get }
}

extensionTogglingwhereSelf == Bool {
var isActive: Bool {
        returnself
    }
}

extensionTogglingwhereSelf == State {
    var isActive: Bool {
returnself == .on
}
}

Default implementations

It is possible to provide default implementations for protocols through extensions. Previously, we provided a partial default implementation for the Toggling protocol on a well-known type. But any other type, that would conform toToggling needs to provide an implementation on isActive. Using another example, let's look at how we can leverage default implementations in protocol extensions without requiring additional conformance work. 

Let's work with a simple protocol, Adder, for the sake of the example:

protocol Adder {
func add(value: Int) -> Int
func remove(value: Int) -> Int
}

The Adder protocol declares two methods: add and remove. And, if we remember our math classes well, we can very well declare remove as a function of add. Removing is just adding a negative value. Protocol extension allows us to do just that:

extensionAdder {
func remove(value: Int) -> Int {
returnadd(value: -value)
    }
}

This may look a bit silly, but in reality, this pattern is really powerful. Remember, we were able to implement remove because we were able to express it as a function of another provided method. Often, in our code, we can implement a method as a function of another. Protocols give us a contract that is fulfilled by either the concrete type or the extension, and we can effectively and expressively compose our programs around those capabilities.

 

Tuples, type aliases, and generics


This chapter would not be complete if we didn't address some very useful features from Swift. Tuples are very useful types that let you return multiple objects as one, without a strongly typed wrapper. Aliases let you quickly define simple type shortcuts. Finally, we'll cover the basics of generics. While generics could be covered in a whole book, we'll just scratch the surface of their syntax, features, and limits, as we'll make use of them extensively throughout this book.

Tuples

Tuples are used to represent a group of values as a single value. Tuples cannot conform to protocols, nor can they inherit. They cannot declare functions in the same way that we can declare a function on a struct or a class. They may look limited, but they have their place as first-class types in the language.

Declaring tuples

Tuples can hold any number of values, from any number of types. You can declare a tuple with the same types—let's say a 2D point in Double:

let origin = (0.0, 0.0)

You can also name the parameters, as follows:

let point = (x: 10.0, y: 10.0)

The two forms are equivalent, but you may want to use the named version, for readability reasons. If you're referencing a size, for example, the tuple would more accordingly be named (width: Double, height: Double). For obvious reasons, this helps to provide a better understanding of your code.

Destructuring tuples

There is a simple method to access tuple values. Take, for example, the size pair, as follows:

let size = (width: 200, height: 400)
let (w, h) = size
let (width, _) = size

In the preceding example, we initialize a tuple on the first line. On the second line, we destructure both parameters as w and h. On the last line is what we call a partial destructuring: when you're only interested in one part of the tuple, you can extract only a part of it. This is useful when dealing with large tuples.

Using tuples in functions

Tuples are first-class citizens in Swift; you can use them, like any other type, as function parameters. The following code demonstrates how to declare a simple function that computes to the Euclidean distance between two points, a and b, represented by tuples:

func distance(_ a: (Double, Double), _ b: (Double, Double)) -> Double {
returnsqrt(pow(b.0 - a.0, 2) + pow(b.1 - a.1, 2))
}
distance(point, origin) == 5.0

You may have noticed that the named parameters of the point tuple are ignored in this case; any pair of Double will be accepted in the method, no matter what they are named.

The opposite is true, as well:

func slope(_ a: (x: Double, y: Double),_ b: (x: Double, y: Double)) -> Double {
return (b.y - a.y) / (b.x - a.x)
}

slope((10, 10), (x: 1, y: 1)) == 1

We've seen examples of using tuples with the same types, but remember that, tuples can contain any type, and as many values as you wish.

Type aliases

Type aliases are a simple addition to the language; they let you reference simple or complex types by an alias. They support all declarations that you can imagine, from the simplest to the most complex.

The following block contains declarations for aliasing the following:

  • A string class into a MyString
  • A function declaration into a Block
  • A block that takes any argument and returns any value
  • A block that takes no argument and returns any value

Let's see the code block; they let you:

typealias MyString = String
typealias Block = () -> Void
typealias TypedBlock<T, U> = (T) -> U
typealias ReturningBlock<U> = () -> U

We could have also defined Block in the function of ReturningBlock:

typealias Block = ReturningBlock<()>

You can also use type aliases for protocol compositions and complex types, as follows:

  • You can declare a type that conforms to a protocol and is of a particular class
  • You can delete a type that conforms to multiple protocols

Let's see an example, as follows:

protocol SomeProtocol {}
protocol OtherProtocol {}

typealias ViewControllerProtocol = NSViewController & SomeProtocol
typealias BothProtocols = SomeProtocol & OtherProtocol

You will often find yourself using type aliases, in order to make your code more readable and more expressive. They are a powerful tool for hiding away some of the implementation complexity or verbosity when declaring long conformances. With type aliases, you can be encouraged to craft many protocols, each with a very small requirement list; then, you can compose all of those protocols when you need them, expressed as those types.

Generics

Generics is a complex subject, and would likely require a full book of its own, for extensive coverage extensively. For the purpose of this book, we'll provide a quick refresher on generics, covering the basics that are required to understand the constructions that we'll use in the different design patterns presented in the next chapters.

Generic functions

In Swift, the simplest form of generics would be the generics in functions. You can use generics very simply, with angled brackets, as follows:

func concat<T>(a: T, b: T) -> [T] {
    return [a,b]
}

The concat method knows nothing about the types that you are passing in, but generics gives us many guarantees over using Any:

  • a and b should be of the same type
  • The return type is an array of elements that have the same type as a and b
  • The type is inferred from the context so you don't have to type it in when you code

You can also leverage protocol conformance in your generic functions, as follows:

protocol Runnable {
    func run()
}

func run<T>(runnable: T) where T: Runnable {
    runnable.run()
}

In this case, the method that is run can only be called with an object that is Runnable.

Generic types

You can also make complex types generic. In our example, we created this wrapper around a list of Runnable, called ManyRunner. The job of a many runner is to run all of the runnables. The ManyRunner is itself Runnable, so we have created a kind of type recursion, as follows:

struct ManyRunner<T>: Runnable where T: Runnable {
    let runnables: [T]
    func run() {
        runnables.forEach { $0.run() }
    }
}

Let's also provide a base object that runs a simple Incrementer. Each time the Incrementer is run, the static count will increment, to keep track of the number of invocations:

struct Incrementer: Runnable {
    private(set) static var count = 0
    func run() {
        Incrementer.count += 1
    }
}

When using generics on types, remember that the types have to be the same:

// This works
let runner = ManyRunner(runnables: [Incrementer(),Incrementer()])
runner.run()
assert(Incrementer.count == 2)
// runner is of type ManyRunner<Incrementer>



ManyRunner(runnables: [Incrementer(), Runners(runnables: [Incrementer()])] as [Runnable]).run()
// This produces the following compile error
// In argument type '[Runnable]', 'Runnable' does not conform to expected type 'Runnable'

We'll look at how to overcome these limitations in Chapter 8Swift-Oriented Patterns.

Generics, protocols, and associated types

You can also use associated types in your protocols. These associated types let you define protocols that are generics, like this: RunnableWithResult. We can implement a bunch of logic and code around the run() method, without actually knowing anything about the return types. We'll encounter this construction many times in this book, so it's important that you're comfortable with associate types:

protocol RunnableWithResult {
    associatedtype ResultType
    func run() -> ResultType
}

struct RunnersWithResult<T>: RunnableWithResult where T: RunnableWithResult {
    let runnables: [T]
    func run() -> [T.ResultType] {
        return runnables.map { $0.run() }
    }
}

Like with generic types, you can't mix and match heterogeneous types. The following example will not compile; later in this book, you'll see strategies for overcoming this common problem when dealing with generics:

struct IntRunnable {
    func run() -> Int { 
        return 0
    }
}

struct StringRunnable {
    func run() -> String {
        return "OK"
    }
}

let runnables: [RunnableWithResult] = [StringRunnable(), IntRunnable()]

This will yield the following dreaded error:

Protocol 'RunnableWithResult' can only be used as a generic constraint because it has Self or associated type requirements
 

Summary


In this chapter, we covered everything that I consider a prerequisite for the rest of this book. We started with classes, the basic building blocks of OOP. You should now be really familiar with them. Structs are unusual constructions for someone coming from OOP, but they are very useful in Swift, as they behave as values, can be immutable, and have other nice properties. With enums, you'll be able to write even more expressive code.

Functions and closures are first-class citizens in Swift, and should be treated as such. Currying is a powerful pattern that lets you reuse functions; in later chapters, you'll see how to use it to write clean code.

The concept of protocols opens the world of protocol extensions and protocol-oriented programming, which is a complex subject. In the following chapters, we'll look at various use cases for implementing particular patterns through protocol extensions.

In the next chapter, we'll focus on memory management and ARC. While value types are not subject to reference counting, classes, functions, and closures interact with each other, and can lead to memory-related crashes and other issues.

About the Authors
  • Florent Vilmart

    Florent Vilmart, M.Sc. is a full stack engineer in Montreal. Born in France, he moved to Montreal short before graduating looking for exciting opportunities in the francophone metropole of North-America. He honed his skills with Objective-C before jumping to Swift when it was released. Hes a polyglot, and you can find his open source work on the parse-community GitHub project where he is one of the core maintainers of the multiple projects. He also spoke at conferences like the GithubCodeConf in 2016 and most recently the Swift Summit in 2017. Currently, he is working full time with Swift, Javascript, and Kubernetes and leads a team of 6 engineers.

    Browse publications by this author
  • Giordano Scalzo

    Giordano Scalzo is a developer with 20 years of programming experience, since the days of ZX Spectrum. He has worked in Swift, Objective-C, C/C++, Java, .Net, Ruby, Python, and in a ton of other languages that he has forgotten the names of. After years of backend development, over the past 10 years, Giordano has developed extensively for iOS, releasing more than 20 apps—apps that he wrote for clients, enterprise applications, or on his own. He is currently a Tech Lead Consultant in London, where he leads mobile digital transformations through his company, Effective Code Ltd.

    Browse publications by this author
  • Sergio De Simone

    Sergio De Simone has been working as a software engineer for over twenty years across a range of different projects and companies, including work environments such as Siemens, HP, and small start-ups. For the last few years, his focus has been on developing mobile platforms and related technologies. He is currently working for BigML, Inc., where he leads iOS and OS X development. Additionally, he likes writing about technology, as well as programming tools, techniques, and languages, with a special focus on Swift evolution, for InfoQ.

    Browse publications by this author
Latest Reviews (2 reviews total)
I put a review of poor because although the general content is ok, lots of examples have mistakes that are misleading (for example wrong variable names from one place to the other, but not only). Also, I still haven't received my hard copy...
Информацията в книгата ми се стори прекалено абстрактна и недостатъчно добре обяснена. Но това е само мое субективно мнение. Възможно е да съм подходил с грешни очаквания, което е довело до тази оценка.
Hands-On Design Patterns with Swift
Unlock this book and the full library FREE for 7 days
Start now