Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Hands-On Design Patterns with Swift
Hands-On Design Patterns with Swift

Hands-On Design Patterns with Swift: Master Swift best practices to build modular applications for mobile, desktop, and server platforms

By Florent Vilmart , Giordano Scalzo , Sergio De Simone
$39.99 $27.98
Book Dec 2018 414 pages 1st Edition
eBook
$39.99 $27.98
Print
$48.99
Subscription
$15.99 Monthly
eBook
$39.99 $27.98
Print
$48.99
Subscription
$15.99 Monthly

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Dec 24, 2018
Length 414 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781789135565
Vendor :
Apple
Category :
Table of content icon View table of contents Preview book icon Preview Book

Hands-On Design Patterns with Swift

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.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Write clean, reusable and maintainable code, and make the most of the latest Swift version.
  • Analyze case studies of some of the popular open source projects and give your workflow a huge boost
  • Choose patterns such as MVP, MVC, and MVVM depending on the application being built

Description

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.

What you will learn

Work efficiently with Foundation and Swift Standard library Understand the most critical GoF patterns and use them efficiently Use Swift 4.2 and its unique capabilities (and limitations) to implement and improve GoF patterns Improve your application architecture and optimize for maintainability and performance Write efficient and clean concurrent programs using futures and promises, or reactive programming techniques Use Swift Package Manager to refactor your program into reusable components Leverage testing and other techniques for writing robust code

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Dec 24, 2018
Length 414 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781789135565
Vendor :
Apple
Category :

Table of Contents

22 Chapters
Title Page Chevron down icon Chevron up icon
Copyright and Credits Chevron down icon Chevron up icon
About Packt Chevron down icon Chevron up icon
Contributors Chevron down icon Chevron up icon
Preface Chevron down icon Chevron up icon
Refreshing the Basics Chevron down icon Chevron up icon
Understanding ARC and Memory Management Chevron down icon Chevron up icon
Diving into Foundation and the Standard Library Chevron down icon Chevron up icon
Working with Objective-C in a Mixed Code Base Chevron down icon Chevron up icon
Creational Patterns Chevron down icon Chevron up icon
Structural Patterns Chevron down icon Chevron up icon
Behavioral Patterns Chevron down icon Chevron up icon
Swift-Oriented Patterns Chevron down icon Chevron up icon
Using the Model-View-Controller Pattern Chevron down icon Chevron up icon
Model-View-ViewModel in Swift Chevron down icon Chevron up icon
Implementing Dependency Injection Chevron down icon Chevron up icon
Futures, Promises, and Reactive Programming Chevron down icon Chevron up icon
Modularize Your Apps with Swift Package Manager Chevron down icon Chevron up icon
Testing Your Code with Unit and UI Tests Chevron down icon Chevron up icon
Going Out in the Open (Source) Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon

Customer reviews

Filter icon Filter
Top Reviews
Rating distribution
Empty star icon Empty star icon Empty star icon Empty star icon Empty star icon 0
(0 Ratings)
5 star 0%
4 star 0%
3 star 0%
2 star 0%
1 star 0%

Filter reviews by


No reviews found
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.