Chapter    20

Protocol-Oriented Programming

When Apple introduced Swift during their Worldwide Developer’s conference in 2014, they promoted it as an easier, safer, and faster programming language for iOS, OS X, and watchOS development than Objective-C. While both Swift and Objective-C allow object-oriented programming, Swift goes one step further and offers protocol-oriented programming as well.

One of the biggest problems with pure object-oriented programming languages is that extending the capabilities of a class means creating additional subclasses. Then you have to create objects based on those new subclasses. This can be a clumsy solution since you now have to keep track of one or more subclasses where each subclass has a different name and may only offer only minor differences between them.

Even worse, Swift (unlike some object-oriented programming languages) only allows single inheritance. That means a class can only inherit from exactly one other class. If you’d really like to inherit properties and methods from two different classes, you can’t.

To get around this problem of inheritance and multiple subclasses, Swift offers protocols. Protocols work much like object-oriented programming but with some additional advantages:

  • Protocols can extend features of classes, structs, and enums. Inheritance can only extend features of classes.
  • A single class, struct, or enum can be extended by one or more protocols. Inheritance can only extend a single class.

This means that protocols are more flexible and versatile than classes while giving you the advantages of object-oriented programming without its disadvantages. Instead of using classes (object-oriented programming), you can use protocols (protocol-oriented programming), or mix both object-oriented programming and protocol-oriented programming so you can get the best of both worlds.

Understanding Protocols

Like a class, a protocol can define properties (variables) and methods (functions). The main difference is that unlike a class, a protocol can’t set an initial value for a property and can’t implement a method. A protocol simply defines a property and its data type. When defining a property in a protocol, you need to define the following three items:

  • The property name, which can be any arbitrary, descriptive name.
  • The data type of the property such as Int, Double, String, etc.
  • Whether a property is gettable { get } or gettable and settable { get set }

 Note  A gettable/settable { get set } property cannot be assigned to a constant stored property (defined with the “let” keyword) or a read-only computed property. A gettable { get } property has no such limitations.

Consider the following protocol:

protocol Cat {
    var name: String { get set }
    var age: Int { get }
}

Now you can create a structure based on this protocol such as:

struct pet : Cat {
    var name : String
    //let name : String -- Invalid since "let" defines a constant stored property
    //var name : String { return "Fred" } -- Invalid since this is a read-only computed property
    let age : Int
}

The above code defines a structure called “pet” that adopts the Cat protocol. That means it needs to declare the name property as a string and the age property as an integer.

Notice that because the age property is defined as gettable { get } by the Cat protocol, you can implement that property as a constant with the “let” keyword. You could also compute the age property by returning a value like this:

struct pet : Cat {
    var name : String
        var age : Int { return 4 }
}

To see how to use protocols and structures with different types of properties, follow these steps:

  1. From within Xcode choose File image New image Playground. Xcode now asks for a playground name.
  2. Click in the Name text field and type ProtocolPlayground.
  3. Make sure the Platform pop-up menu displays OS X.
  4. Click the Next button. Xcode asks where you want to store the playground.
  5. Choose a folder to store your project and click the Create button.
  6. Edit the playground code as follows:
    import Cocoa

    protocol Cat {
        var name: String { get set }
        var age: Int { get }
    }

    // Using a computed property
    struct pet : Cat {
        var name : String
        var age : Int { return 4 }
    }

    var animal = pet(name: "Taffy")
    print (animal.name)
    print (animal.age)

    // Using a constant stored property with "let"
    struct feral : Cat {
        var name : String
        let age : Int
    }

    var pest = feral (name: "Stinky", age: 2)
    print (pest.name)
    print (pest.age)

Notice the differences when you declare a variable based on a structure. With computed properties for age, you don’t need to create an initial value when declaring the variable such as:

// Using a computed property { return 4 }
struct pet : Cat {
    var name : String
    var age : Int { return 4 }
}
var animal = pet(name: "Taffy")

However if you don’t have computed properties, then you must specifically initialize it when creating a variable such as:

// Using a constant stored property with "let"
struct feral : Cat {
    var name : String
    let age : Int
}
var pest = feral (name: "Stinky", age: 2)

Figure 20-1 shows how you can define two structures based on the same protocol.

9781484212349_Fig20-01.jpg

Figure 20-1. Two different structures can adopt the same protocol

Using Methods in Protocols

When a protocol defines a method, it only defines the method name, any parameters, and any data type it returns, but it does not include any Swift code that actually implements that method such as:

protocol Cat {
    var name: String { get set }
    var age: Int { get }
        func meow (sound: String)
}

If a class, struct, or enum adopts this Cat protocol, it must not only define a name and age property, but it must also implement the protocol method with Swift code such as:

struct pet : Cat {
    var name : String
    var age : Int
    func meow (sound : String) {
        print (sound)
    }
}

Even though a class, structure, or enum might adopt the exact same protocol, the actual implementation of a method can be wildly different such as:

struct feral : Cat {
    var name : String
    var age : Int
    func meow (sound : String) {
        print ("Hear me roar")
        print (sound.uppercaseString)
    }
}

To see how to define methods in protocols and implement them in different structures, follow these steps:

  1. Make sure the ProtocolPlayground file is loaded in Xcode.
  2. Edit the playground code as follows:
    import Cocoa

    protocol Cat {
        var name: String { get set }
        var age: Int { get }
        func meow (sound : String)
    }

    // One way to implement the meow method
    struct pet : Cat {
        var name : String
        var age : Int
        func meow (sound : String) {
            print (sound)
        }
    }

    var animal = pet(name: "Taffy", age: 16)
    print (animal.name)
    print (animal.age)
    animal.meow("Feed me")methods

    // A second way to implement the same meow method
    struct feral : Cat {
        var name : String
        var age : Int
        func meow (sound : String) {
            print ("Hear me roar")
            print (sound.uppercaseString)
        }
    }

    var pest = feral(name: "Stinky", age: 2)
    print (pest.name)
    print(pest.age)
    pest.meow("Growl")

Figure 20-2 shows how the two different implementations of the same protocol method work.

9781484212349_Fig20-02.jpg

Figure 20-2. Method implementations can be different despite adopting the same protocol

Adopting Multiple Protocols

One huge advantage protocols have over object-oriented programming is that a single class, structure, or enum can adopt one or more protocols. To do this, you just need to specify the name of each protocol to adopt such as:

struct structureName : protocolName1, protocolName2, protocolNameN {

By letting you adopt multiple protocols, Swift makes coding more flexible since you can pick and choose the protocols you want to use. To see how adopting multiple protocols works, follow these steps:

  1. Make sure the ProtocolPlayground file is loaded in Xcode.
  2. Edit the playground code as follows:
    import Cocoa

    protocol Cat {
        var name: String { get }
        var age: Int { get }
    }

    protocol CatSounds {
        func meow (sound : String)
    }

    struct pet : Cat, CatSounds {
        var name : String
        var age : Int
        func meow (sound : String) {
            print (sound)
        }
    }

    var animal = pet(name: "Fluffy", age: 6)
    print (animal.name)
    print (animal.age)
    animal.meow("Wake up!")

Figure 20-3 shows how this code works by having a structure adopt two protocols. Notice that this code works exactly the same as if the name and age properties were defined in the same protocol as the meow method. By separating different properties and methods in multiple protocols, you don’t have to implement properties and/or methods you don’t need.

9781484212349_Fig20-03.jpg

Figure 20-3. A structure can adopt two or more protocols

Since you can adopt multiple protocols, it’s generally best to keep each protocol as short and simple as possible. This makes it easier to use only the protocols you need without being forced to implement additional properties and/or methods you may not need or want.

Protocol Extensions

In the world of object-oriented programming, you can extend a class through inheritance, which inherits every property and method that class may have inherited from other classes. In the world of protocol-oriented programming, you can extend a protocol through protocol extensions.

One purpose for protocol extensions is to define default values for properties. You could define default values by simply assigning values to one or more properties such as:

protocol Cat {
    var name: String { get }
    var age: Int { get }
}
This code defines a protocol called Cat, which defines a name and age property as gettable { get }. Then you can create a structure that adopts the Cat protocol and assigns an initial value to each property such as:
struct pet : Cat {
    var name : String = "Frank"
    var age : Int = 2
}

Now if you declare a variable based on this structure, the variable contains those initial values. Despite having an initial value, you can always store new data in those properties. To see how to define initial values for protocol properties without using extensions, follow these steps:

  1. Make sure the ProtocolPlayground file is loaded in Xcode.
  2. Edit the playground code as follows:
    import Cocoa

    protocol Cat {
        var name: String { get }
        var age: Int { get }
    }

    struct pet : Cat {
        var name : String = "Frank"
        var age : Int = 2
    }

    var animal = pet()
    print (animal.name)
    print (animal.age)

    animal.name = "Joey"
    animal.age = 13
    print (animal.name)
    print (animal.age)

When you first create the animal variable based on the pet structure, the initial name property value is “Frank” and the initial age property value is 2. At any time, you can store a new value in both properties as shown in Figure 20-4.

9781484212349_Fig20-04.jpg

Figure 20-4. You can assign initial values to gettable properties defined by a protocol

If you want to set (and keep) an initial value for a property, that’s when you can use protocol extensions. Protocol extensions can only work for gettable { get } properties. A protocol extension just uses the extension keyword followed by the name of the protocol you want to extend such as:

extension Cat {
    // Set one or more default values for gettable properties here
}

Inside the extension you can compute a default value for one or more gettable properties by using the return keyword such as:

extension Cat {
    var name : String { return "Frank" }
}

Not only does the protocol extension define the name property to hold the string “Frank,” but it also eliminates the need to define the name property within a class, structure, or enum such as:

extension Cat {
    var name : String { return "Frank" }
}

struct pet : Cat {
    var age : Int = 2
}

The above protocol extension and structure is essentially equivalent to this:

struct pet : Cat {
    var name : String = "Frank"
    var age : Int = 2
}

The main difference is that when the name property is declared inside the structure, you can assign new strings to the name property later. When the name property is declared and set to a value inside a protocol extension, you cannot assign a new string to the name property later. To see how protocol extensions define a default value, follow these steps:

  1. Make sure the ProtocolPlayground file is loaded in Xcode.
  2. Edit the playground code as follows:
    import Cocoa

    protocol Cat {
        var name: String { get }
        var age: Int { get }
    }

    extension Cat {
        var name : String { return "Frank" }
    }

    struct pet : Cat {
        //var name : String = "Frank"
        var age : Int = 2
    }

    var animal = pet()
    print (animal.name)
    print (animal.age)

    //animal.name = "Joey"
    animal.age = 13
    print (animal.name)
    print (animal.age)

In the above code, the two commented lines show you what the protocol extension eliminates. Once you define the name property inside the protocol extension, you no longer need to declare that name property inside the pet structure. You also can’t assign a new string (such as “Joey”) to the name property after it’s been assigned a default value in the protocol extension. Figure 20-5 shows the results of the above code.

9781484212349_Fig20-05.jpg

Figure 20-5. Defining a default value with a protocol extension

One problem with defining a default value in a protocol extension is that anything that adopts that protocol is forced to also adopt that protocol’s extension. That means everything based on that protocol and extension will have the same default value that can’t be changed.

In case you want the flexibility of defining default values, but only for certain classes, structures, or enums, you can create a protocol extension that only applies to any class, structure, or enum that also adopts a second protocol:

extension protocol2Extend where Self: protocol2Use {
    // Set one or more default values for gettable properties here
}

The above Swift code extends a protocol defined by “protocol2Extend.” However the “where Self:” keywords identify a second protocol defined by “protocol2Use.”

That means if a class, structure, or enum adopts the protocol defined by “protocol2Extend,” then the extension only sets a default value if the class, structure, or enum also adopts the second protocol defined by “protocol2Use.”

Suppose you created the following protocol extension:

extension Cat where Self: CatType {
    var name : String { return "Frank" }
}

This would define a default value of “Frank” to the name property to any class, structure, or enum that adopts the Cat protocol and the CatType protocol. If a class, structure, or enum only adopts the Cat protocol, its name property won’t be assigned a default value of “Frank.”

To see how this version of a protocol extension only works when a class, structure, or enum adopts a specific protocol, follow these steps:

  1. Make sure the ProtocolPlayground file is loaded in Xcode.
  2. Edit the playground code as follows:
    import Cocoa

    protocol Cat {
        var name: String { get }
        var age: Int { get }
    }

    protocol CatType {
        var species: String { get }
    }

    extension Cat where Self: CatType {
        var name : String { return "Frank" }
    }

    struct pet : Cat {
        var name : String = "Max"
        var age : Int = 2
    }

    var animal = pet()
    print (animal.name)
    print (animal.age)

    animal.name = "Joey"
    animal.age = 13
    print (animal.name)
    print (animal.age)

    // New structure that adopts two protocols
    struct wild : Cat, CatType {
        var age : Int = 2
        var species : String = "Lion"
    }

    var beast = wild()
    print (beast.name)
    print (beast.age)
    print (beast.species)

Notice that pet structure adopts the Cat protocol but since it doesn’t adopt the CatType protocol as well, you can assign an initial value of “Max” to its name property, and you can later assign “Joey” to that same name property.

However the wild structure adopts both the Cat and CatType protocols, so it also adopts the protocol extension, which assigns “Frank” as a default value to the wild structure’s name property as shown in Figure 20-6.

9781484212349_Fig20-06.jpg

Figure 20-6. Protocol extensions can selectively assign default values to a structure that adopts a specific protocol

Using Protocol Extensions to Extend Common Data Types

Perhaps the most interesting use for protocol extensions is to add properties to common data types such as String, Int, and Double. A protocol extension for extending data types looks like this:

extension dataType {
  // New property {
         // return computed value
  }
}

When you extend a common data type with a protocol, you must create a variable that represents the data type followed by a period and the property name. So if you created an extension for the Int data type like this:

extension Int {
    var name : String {
        return text
      }
}

You could store a string value like this:

var status = 25.name   // represents a String

To see how to use protocol extensions to extend common data types, follow these steps:

  1. Make sure the ProtocolPlayground file is loaded in Xcode.
  2. Edit the playground code as follows:
    import Cocoa

    func checkMe (myAge : Int) -> String {
        if myAge >= 21 {
            return "Legal"
        } else {
            return "Underage"
        }
    }

    var text : String
    var myAge : Int

    myAge = 32
    text = checkMe (myAge)

    extension Int {
        var name : String {
            return text
          }
    }

    var status = myAge.name
    print (myAge)
    print (status)

    myAge = 16
    text = checkMe (myAge)
    status = myAge.name
    print (myAge)
    print (status)

Figure 20-7 shows the result of running this code. As you can see, when the myAge variable is greater than 21, its name property stores a string, which gets stored in the status variable. Depending on the value of the myAge variable, the name property stores either the string “Legal” or the string “Underage.”

9781484212349_Fig20-07.jpg

Figure 20-7. Protocol extensions can extend common data types

Summary

Protocols offer an alternate way to extend existing code. Just like a class, a protocol can define properties and methods. The main difference is that a protocol only defines a method’s name and parameters, but does not actually include Swift code that implements that method.

Another difference is that when a protocol defines properties, it must also define whether that property is gettable { get } or gettable and settable { get set }.

Protocols work much like inheritance. While inheritance only allows classes to inherit from exactly one other class, protocols can extend classes, structures, and enums using one or more protocols.

Protocol extensions let you define default values for properties. When combined with common data types such as Int, String, and Double, protocol extensions allow a data type to store additional information.

Just remember that you can mix object-oriented programming with protocol-oriented programming so you don’t have to choose between one or the other. Both objects and protocols can be useful depending on what you need to accomplish in your particular program. Make sure you understand how to define protocols, use them, and extend them because protocols will be a common feature of Swift programming.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset