Skip to content

Classes, part 5

Informations presented on this page may be incomplete or outdated. You can find the most up-to-date version reading my book Learn Swift by examples. Beginner level

Prepared and tested with Xcode 9.0 and Swift 4.0

In this tutorial we cover the following topics


Protocols

A protocol is a group of related properties and methods that can be implemented by any class. With protocols we can define a one single API and use it (after implementation) in unrelated classes. This way we can represent horizontal relationships on top of an existing tree-like class hierarchy (see graph below).

Todo (id=no todo id): image: protocols as horizontal relationship (both in Swift and in Objective-C

In this sense protocols are like Java interfaces.

A syntax of protocol is shown below.

As we can see, there is no method to say that something must be implemented or may be implemented (like @required and @optional keyword in Objective-C. Saying the truth, we can do this but optional requirements are available so that we can write code that interoperates with Objective-C. This is not native Swift feature.

Here is the syntax for class ClassName having a superclass SuperclassName conforming to protocol ProtocolName1 as well as ProtocolName2


Requirements: creating and adopting protocols

A protocol can require any conforming type to provide an instance property or type property with a particular name and type. The protocol doesn’t specify whether the property should be a stored property or a computed property. The protocol also specifies whether each property must be gettable or gettable and settable.

Protocols can require specific instance methods and type methods to be implemented. These methods are written as part of the protocol’s definition in exactly the same way as for normal instance and type methods, but without curly braces or a method body. Variadic parameters are allowed, subject to the same rules as for normal methods. Default values, however, can’t be specified for method parameters within a protocol’s definition.

It’s sometimes necessary for a method to modify (or mutate) the instance it belongs to. In such a case, we place the mutating keyword before a method’s func keyword to indicate that the method is allowed to modify the instance it belongs to and any properties of that instance.

Protocols can require specific initializers to be implemented. We write these initializers as part of the protocol’s definition in exactly the same way as for normal initializers, but without curly braces or an initializer body. When implemented, a protocol initializer must be marked with the required keyword.

Let's see how it works in the following example.


6
9
6
9


Protocols inheritance

A protocol can inherit one or more other protocols and can add further requirements on top of the requirements it inherits. The syntax for protocol inheritance is similar to the syntax for class inheritance, but with the option to list multiple inherited protocols, separated by commas:


6
9
6
9


Protocols as types

Although protocols don’t provide any real functionality themselves they are a fully-fledged type we can use in our code. In consequence, we can use protocols in many places where other types are allowed

  • we can use protocols as a parameter type or return type in a function, method, or initializer;
  • we can use protocols as the type of a constant, variable, or property;
  • we can use protocols as the type of items in an array, dictionary, or other container.

We can use the is and as operators to check for protocol conformance, and to cast to a specific protocol. Doing this follows the same, well known, syntax as for type

  • The is operator returns true if an instance conforms to a protocol and returns false if it doesn’t.
  • The as? version of the downcast operator returns an optional value of the protocol’s type, and this value is nil if the instance doesn’t conform to that protocol.
  • The as! version of the downcast operator forces the downcast to the protocol type and triggers a runtime error if the downcast doesn’t succeed.


1
1
[Classes_05.TestClass1, Classes_05.TestClass1_2]


Protocols composition

It can be useful to require a type to conform to multiple protocols at the same time. We can combine multiple protocols into a single requirement with an inheritance defining new protocol types that has the combined requirements of all protocols. In Swift we have a mechanism, named protocol composition, doing this for us. Protocol compositions behave as if we defined a temporary local protocol that has the combined requirements of all protocols in the composition. Protocol compositions don’t define any new protocol types.

Protocol compositions have the form SomeProtocol & AnotherProtocol where ampersands are used to separate multiple protocols.


6
9
6
9
9
12


The end or not the end

This is the essence of protocols. For more crazy details, please refer to The Swift Programming Language (Swift 4): Protocols


Extensions

With an extensions we can add a new functionality (but we cannot override existing functionality) to an existing code: class, structure, enumeration, or protocol type. This includes the ability to extend types for which we do not have access to the original source code. Extensions are similar to categories in Objective-C, because they allows to extend types without original source code, and are similar to extensions in Objective-C, because they do not have names (in short, categories without name are extensions in Objective-C).

With extensions we can

  • Case 1. Add computed instance properties and computed type properties.
  • Case 2. Define instance methods and type method. Instance methods added with an extension can also modify (or mutate) the instance itself.
  • Case 3. Provide new initializers. This enables us to extend other types to accept our own custom types as initializer parameters, or to provide additional initialization options that were not included as part of the type’s original implementation.
  • Case 4. Define subscripts.
  • Case 5: Define and use new nested types.
  • Case 6: Make an existing type conform to a protocol.
  • Case 7: Extend a protocol to provide implementations of its requirements or add additional functionality that conforming types can take advantage of. This allows us to define behavior on protocols themselves, rather than in each type’s individual conformance or in a global function. By creating an extension on the protocol, all conforming types automatically gain this extension implementation without any additional modification.
  • Case 8: Add new subscripts to an existing type.


3
4
5
555
positive
-5
negative
positive


Generics

Generics are a one of my favorite feature. With this we can write very flexible and reusable code without duplicating it only because we want it to work with different type(s).


Generic functions

Generic functions can work with any type. Any time we have a functionality common for different types we can write a universal (generic) code where instead of real type a placeholder type is used. In Swift a placeholder type, known as a type parameter, specify its name, and is written immediately after the function’s name, between a pair of matching angle brackets (such as <T> for T type parameter). The generic version of the function uses a placeholder type name (T, in our case) instead of an actual type name (such as Int, or String). The actual type to use in place of T is determined every time a function is used.


[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
["one", "two", "three", "four", "five"]
["five", "four", "three", "two", "one"]


Type constraints in generic functions

Although generics functions can work with any type, it’s sometimes useful to enforce certain type constraints on the types that can be used. Type constraints specify that a type parameter must inherit from a specific class, or conform to a particular protocol or protocol composition. Type constraints concerns generic functions as well as generic types (see next subsection).


Generic types

Generic types are maybe even more often used than generic functions. The most basic examples are quite natural: basic data structures like arrays, dictionaries, stacks or queues.


one
two
three
four
five
undefined


Extending a generic types

When extending a generic type, we don’t provide a type parameter list as part of the extension’s definition. Instead, the type parameter list from the original type definition is available within the body of the extension, and the original type parameter names are used to refer to the type parameters from the original definition.

Based on the previous code, let's add an extension to it.


one


Generic protocols: associated types

When defining a protocol, it’s sometimes useful to declare one or more associated types as part of the protocol’s definition. This is a way we make generic protocols. An associated type gives a placeholder name to a type that is used as part of the protocol. The actual type to use for that associated type isn’t specified until the protocol is adopted. Associated types are specified with the associatedtype keyword.

Now we can use the extended protocol either in nongeneric way


none
none
two

or in generic way


none
none
two

Note, that in generic case we don't have to use typealias keyword.


The end or not the end

This is the essence of generics. As for protocols, for more crazy details, please refer to The Swift Programming Language (Swift 4): Generics