In this section, we will look at how to define a protocol and how to add requirements to it. This will give us a basic understanding of the protocol. The rest of this chapter will build on this understanding.
Protocol syntax
Defining a protocol
The syntax we use to define a protocol is very similar to the syntax that's used to define a class, structure, or enumeration. The following example shows the syntax that's used to define a protocol:
protocol MyProtocol { //protocol definition here }
To define the protocol, we use the protocol keyword, followed by the name of the protocol. We then put the requirements, which our protocol defines, between curly brackets. Custom types can state that they conform to a particular protocol by placing the name of the protocol after the type's name, separated by a colon. The following example shows how we would define that a structure conforms to a protocol:
struct MyStruct: MyProtocol { //Structure implementation here }
A type can also conform to multiple protocols. We list the multiple protocols that the type conforms to by separating them with commas:
struct MyStruct: MyProtocol, AnotherProtocol, ThirdProtocol { //Structure implementation here }
Having a type conform to multiple protocols is a very important concept within protocol- oriented programming, as we will see later in this chapter and throughout this book. This concept is known as protocol composition. Now, let's see how we would add property requirements to our protocol.
Property requirements
A protocol can require that the conforming types provide certain properties with specified names and types. The protocol doesn't say whether the property should be a stored or computed property because the implementation details are left up to the conforming types.
When defining a property within a protocol, we must specify whether the property is a read-only or a read-write property by using the get and set keywords. We also need to specify the property's type since we cannot use type inference in a protocol. Let's look at how we would define properties within a protocol by creating a protocol named FullName, as shown in the following example:
protocol FullName { var firstName: String {get set} var lastName: String {get set} }
In this example, we define two properties named firstName and lastName, which are read-write properties. Any type that conforms to this protocol must implement both of these properties. If we wanted to define the property as read-only, we would define it using only the get keyword, as shown in the following code:
var readOnly: String {get}
It is possible to define static properties by using the static keyword, as shown in the following example:
static var typeProperty: String {get}
Static properties are properties that are owned by the type and shared by all instances. This means that if one instance changes the value of this property, then the value changes for all instances. We will look at how to use static instances more when we look at the singleton pattern.
Now, let's look at how we would add method requirements to our protocol.
Method requirements
A protocol can require that the conforming types provide specific methods. These methods are defined within the protocol exactly as we define them within a class or structure, but without the curly brackets and method body. We can define that these methods are instance or type methods using the static keyword. Adding default values to the method's parameters is not allowed when defining the method within a protocol.
Let's add a method named getFullName() to the FullName protocol:
protocol FullName { var firstName: String {get set} var lastName: String {get set} func getFullName() -> String }
The FullName protocol now requires one method named getFullName() and two read- write properties named firstName and lastName.
For value types, such as the structure, if we intend for a method to modify the instances that it belongs to, we must prefix the method definition with the mutating keyword. This keyword indicates that the method is allowed to modify the instance it belongs to. The following example shows how to use the mutating keyword with a method definition:
mutating func changeName()
If we mark a method requirement as mutating, we don't need to write the mutating keyword for that method when we adopt the protocol with a reference (class) type. The mutating keyword is only used with value (structures or enumerations) types.
Optional requirements
There are times when we want protocols to define optional requirements. An optional requirement is a method or property that doesn't need to be implemented. To use optional requirements, we need to start off by marking the protocol with the @objc attribute.
To mark a property or method as optional, we use the optional keyword. The following example shows how we would create both an optional property and also an optional method:
@objc protocol Phone { var phoneNumber: String {get set} @objc optional var emailAddress: String {get set} func dialNumber() @objc optional func getEmail() }
If we are using the @objc attribute, as shown in the previous example, we cannot use the mutating keyword because it isn't valid for classes. Now, let's explore how protocol inheritance works.