Swift Overview - Protocols and Extensions

Protocols and extensions are both tools swift uses to package modules of functionality that expand upon classes, enums and structs.

Protocols can be shared across classes, and extensions are used to customize existing classes. Both provide ways to reuse code and adhere to DRY principles

Protocol

A protocol is a collection of related method signatures and property declarations.

Instead of forcing the caller of an API to pass a specific class, struct, or enum, an API can let callers pass any class/struct/enum that the caller wants, but can require that they implement certain methods and/or properties that the API wants it to.

When a swift class, enum or struct adopts a protocol it's like signing a contract to implement specified methods in the collection.

This provides us a powerful high level abstraction, in that we can reason that anything that conforms to Apple’s comparable protocol can be compared.

A protocol is a first class type.

The implementation of a protocol's methods and properties is done by an implementing type (any class, struct, or enum), or the type the "conforms" to that protocol. This is because a protocol can have no storage associated with it. It is also possible to add implementation to a protocol via an extension to that protocol (but remember that extensions also cannot use any storage).

A protocol can have optional methods, which do not need to be implemented by the type that is conforming to it. Normally any protocol implementor must implement all the methods or properties in the protocol, however it is possible to mark some methods in a protocol as optional (has nothing to do with the Optional type). Any protocol that has optional methods must be marked @objc. And any optional-protocol implementing class must inherit from NSObject. These types of protocols with optional methods are an essential piece of the delegate pattern, by which one type calls upon another type to perform some operation. Except for delegation, a protocol with optional methods is rarely used.

Syntax

Much like a class or struct definition, it begins with the word protocol, followed by the protocol name and then in between curly braces we define tis properties and methods. In this example you can see that the methods only define return types and parameters but are not implemented. Implementation is left up to the conforming type.

protocol Souschef {  
  func chop(vegetable: String) -> String
  func rinse(vegetable: String) -> String
}

All properties in a protocol must specify if it is read/write or just read with the get and set keywords.

protocol MyProtocol {  
  var someProperty: Int { get set }
  var someOtherProperty: Int { get }
}

Additionally if a method is going to mutate the type implementing the protocol the method must be marked with the mutation keyword.

protocol MyOtherProtocol {  
  mutating func changeIt(arg: String)
}

We can also specify an init which will require any type that conforms to the protocol to need to be able to initialized with the arguments in the init function. If a class is conforming to a protocol that has an init method it must be marked as required, because all of it subclasses must also conform to this protocol

protocol MyOtherOtherProtocol {  
  init(arg1: String, arg2: String)
}

Adopting a protocol is done with a colon followed by the name of the protocol after the class name declaration. Here the Roommate class is signing a contract to implement the two methods in the the Soushef protocol.

class Roommate: Souschef  {  
  var hungry = true
  var name: String

  init(hungry: Bool, name: String) {
    self.hungry = hungry
    self.name = name
  }

  func chop(vegetable: String) -> String {
    return "She's choppin' \(vegetable)!”
  }

 func rinse(vegetable: String) -> String {
   return "The \(vegetable) is so fresh and so clean"
  }
}

Expanding on the previous file in the following we are also adopting the a protocol from the Swift standard library called Equatable. To conform to this protocol we need to implement the only function defined in the protocol which is named ==. We implement this outside of the class because operators are global functions.

The protocol signature looks like this.

public protocol Equatable {  
/// Returns a Boolean value indicating whether two values are equal.   
///    
/// Equality is the inverse of inequality. For any values `a` and `b`,    
/// `a == b` implies that `a != b` is `false`.    
///   
/// - Parameters:    
///   - lhs: A value to compare.    
///   - rhs: Another value to compare.    
public static func ==(lhs: Self, rhs: Self) -> Bool  
}

And class with func calls.

class Roommate: Souschef, Equatable {  
  var hungry = true
  var name: String

  init(hungry: Bool, name: String) {
    self.hungry = hungry
    self.name = name
  }

  func chop(vegetable: String) -> String {
    return "She's choppin' \(vegetable)!”
  }

 func rinse(vegetable: String) -> String {
   return "The \(vegetable) is so fresh and so clean"
  }
}

func ==(lhs: Roommate, rhs: Roommate) -> Bool {  
  return lhs.name == rhs.name && lhs.hungry == rhs.hungry
}

var tom = Roommate(hungry: true, name: “Tom”)  
var jeff = Roommate(hungry: true, name: "Jeff")

tom == jeff // returns true  

Protocol As A Type

A protocol is also a type, you can use a protocol name in all of the places you would normally use any other type. To describe variables, constants, and properties, to indicate parameter and return types, and to indicate the item types in a collection.

In this class we are saying that all of the members need to implement the SousChef protocol, but can be of any class, enum or struct.

This is strange because SousChef is not a class or struct itself and has no logic of it’s own. So to specify SousChef as a type is to say that the class DinnerCrew does not care what type goes here as long as it adopts a SousChef protocol.

This is done by indicating SousChef where we would otherwise write the type of the items in the array, and do the same for the parameters in the init method.

class DinnerCrew {  
  var members: [Souschef]

  init(members: [Souschef]) {
    self.members = members
  }
}
class RandomPasserby: Souschef {  
  var name: String

  init(name: String) {
    self.name = name
  }

  func chop(vegetable: String) -> String {
    return "She's choppin' \(vegetable)!”
  }

  func rinse(vegetable: String) -> String {
    return "The \(vegetable) is so fresh and so clean"
  }
}

var newFriend = RandomPasserby(name: "Dave”)  
var motleyDinnerCrew = DinnerCrew(members:[newFriend, tom])  

Extensions

Kind of like adding an extension to a house, extensions offer a way to add computed properties and methods to an existing class, struct, or enum. They are often used to extend types for which you don’t have access to the code, like Apple Frameworks or third party libraries.

To create extensions we use the keyword extension followed but the class we are extending.

Inside extensions when a reference to self is made, it is referring to the class its in.

Some limitations to extensions are that we cannot re-implement methods or properties that already exist in the object we are extending, so in other word we can't override anything in an extension, we can only add new "things". Another huge restriction extensions have is they are not able to implement any storage, the only vars an extension is legally allowed to have are computed properties.

Extensions can easily be abused. They should be used to add clarity to the readability of the code, not obfuscation. We should not use it as a substitute for good object-oriented design technique, in that extensions should be well-contained helper functions. That being said they can be used well to organize code but requires architectural commitment, so when in doubt, don't use them.

This is an example from Andrew Bancroft’s Swift Blog, which extends the UIView class and enables the view to fade out and then fade back in. Its not immediately apparent from he following but the imageView that has fadeOut and fadeIn called on it belongs to UIView. We need an extension because we do not have access to modify the underlying UIView code.

class ViewController: UIViewController {

    // MARK: Outlets

    @IBOutlet weak var imageView: UIImageView!

    // MARK: Actions

    @IBAction func sunRiseAndSet(_ sender: AnyObject) {
        // Fade out
        imageView.fadeOut(1.0, delay: 0.0, completion: {
            (finished: Bool) -> Void in

            // Once the imageView is invisible, set the image property to a new value
            if (self.imageView.image == UIImage(named: "sunrise")) {
                self.imageView.image = UIImage(named:"sunset")!
            } else {
                self.imageView.image = UIImage(named:"sunrise")!
            }

            // Then fade the image back in
            self.imageView.fadeIn(1.0, delay: 0.0, completion: nil)
        })
    }
}

Snippet from the extension

extension UIView {  
    func fadeIn(duration: NSTimeInterval = 1.0, delay: NSTimeInterval = 0.0, completion: ((Bool) -> Void) = {(finished: Bool) -> Void in}) {
        UIView.animateWithDuration(duration, delay: delay, options: UIViewAnimationOptions.CurveEaseOut, animations: {
            self.alpha = 1.0
            }, completion: completion)  }

    func fadeOut(duration: NSTimeInterval = 1.0, delay: NSTimeInterval = 0.0, completion: (Bool) -> Void = {(finished: Bool) -> Void in}) {
        UIView.animateWithDuration(duration, delay: delay, options: UIViewAnimationOptions.CurveEaseOut, animations: {
            self.alpha = 0.0
            }, completion: completion)
    }
}