Swift - Protocol
Published: 2022-04-06 09:17:11

A protocol defines a blueprint of methods, properties, and other requirements that can 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.

- A Protocol can be extended to implement some of the requirement specified by itself or Implement additional functionality.

---

Protocol can specify the following requirement :

1. Property requirement
2. Method Requirement
3. Mutating Method Requirement
4. Initializer Requirement (incl Failable Initializer)

### Property Requirements

- A protocol can require any conforming type to provide an **instance property** or **type property** with a particular name and type.

- It specifies whether each property must be gettable or gettable and settable.

- doesn’t specify whether the property should be a stored property or a computed property (Conforming type can implement it as either)

If a protocol requires a property to be :

  1. **gettable and settable** - conforming type cannot use constant stored property or a read-only computed property.
  2. **only gettable** - conforming type can use any kind of property.

- Property requirements are always declared as variable properties, prefixed with the var keyword. But conforming type can declare the property as constant if required. 

example :

``` swift
protocol someProtocol{
    var property : Int { get }
}

class someClass : someProtocol{
    let property : Int
    
    init( _ property : Int){
        self.property = property
    }
}
```
- For **type property**, use **static** keyword in requirement.

---

### Method Requirements

A protocol can require any conforming type to implement an **instance methods** and **type methods**.

- Default values can’t be specified for method parameters within a protocol’s definition.

- For **type method**, use **static** keyword in requirement.

example :

``` swift
protocol RandomNumberGenerator {
    func random() -> Double
}
```
---

### Mutating Method Requirements

A protocol can require any conforming type to implement an mutating method by marking the method with the mutating keyword as part of the protocol’s definition. 

example :

``` swift
protocol Togglable {
    mutating func toggle()
}
```

``` swift
enum OnOffSwitch: Togglable {
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
```

- Mutating keyword is not required when writing an implementation of that method for a class.

---
### Initializer Requirements
A protocol can require any conforming type to implement specific initializers.

- A type can conform protocol initializer requirement as **designated initializer** or **convenience  initailizer**. In both the cases **required** modifier should be prefixed.

- required modifier ensures that you provide an explicit or inherited implementation of the initializer requirement on all subclasses of the conforming class.

- required modifier is not needed for classes that are marked with the final modifier, because that cannot be subclassed.

- If a subclass overrides a designated initializer from superclass and also implements a matching initializer requirement from a protocol, use both the required and override modifiers:

example :

``` swift
protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
        // initializer implementation goes here
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    required override init() {
        // initializer implementation goes here
    }
}
```

- Protocols can define **failable** initializer requirements for conforming types. 

- A failable initializer requirement can be satisfied by a failable or nonfailable initializer on a conforming type. A nonfailable initializer requirement can be satisfied by a nonfailable initializer or an implicitly unwrapped failable initializer.

---

### Protocols as Types

You can use a protocol as a type in many places where other types are allowed, including:

1. As a parameter type or return type in a function, method, or initializer.
2. As the type of a constant, variable, or property.
3. As the type of items in an array, dictionary, or other container.

- A protocol type can be downcasted to an underlying type in the same way superclass downcast to a subclass.



---
### Adding Protocol Conformance with an Extension

- **Existing type** can be extended to adopt and conform to a new protocol, even if you don’t have access to the source code for the existing type.
- **Existing instances** of a type automatically adopt and conform to a protocol when that conformance is added to the instance’s type in an extension.

---

### Conditionally Conforming to a Protocol

A **generic type** may be able to satisfy the requirements of a protocol only under certain conditions, such as when the type’s generic parameter conforms to the protocol.

A generic type conditionally conform to a protocol by listing constraints when extending the type by including generic **where** clause

example :

``` swift
class Dice {
    let sides: Int
    let generator: RandomNumberGenerator
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
```

``` swift
protocol TextRepresentable {
    var textualDescription: String { get }
}

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
```

``` swift
extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription) // Prints "[A 6-sided dice, A 12-sided dice]"

```
---

### Declaring Protocol Adoption with an Extension

If a type already conforms to all of the requirements of a protocol, but hasn’t yet stated that it adopts that protocol, you can make it adopt the protocol with an empty extension:

example :

``` swift
struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}
```
---

### Adopting a Protocol Using a Synthesized Implementation
Swift can automatically provide the protocol conformance for **Equatable**, **Hashable**, and **Comparable** in many simple cases.

Swift provides a synthesized implementation of **Equatable** for the following kinds of custom types:

  1. Structures that have only stored properties that conform to the Equatable protocol
  2. Enumerations that have only associated types that conform to the Equatable protocol
  3. Enumerations that have no associated types

Swift provides a synthesized implementation of **Hashable** for the following kinds of custom types:

  1. Structures that have only stored properties that conform to the Hashable protocol
  2. Enumerations that have only associated types that conform to the Hashable protocol
  3. Enumerations that have no associated types

Swift provides a synthesized implementation of **Comparable** for : 

  1. Enumerations that don’t have a raw value. 
  2. If the enumeration has associated types, they must all conform to the Comparable protocol.
 
---

- A protocol can inherit **one or more** other protocols and can add further requirements on top of the requirements it inherits.

- You can limit protocol adoption to class types (and not structures or enumerations) by adding the **AnyObject** protocol to a protocol’s inheritance list.

example :

``` swift
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}
```

- **Protocol Composition** - multiple protocols can be combined into a single requirement by separating them with ampersands (&).

example :

``` swift
protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
```
---

### Optional Protocol Requirements

- Optional requirements can be defined for protocols. These requirements don’t have to be implemented by types that conform to the protocol. Optional requirements are prefixed by the **optional** modifier

- Optional requirements are available so that you can write code that interoperates with **Objective C**. Both the protocol and the optional requirement must be marked with the @objc attribute. Note that `@objc` protocols can be adopted only by classes that inherit from Objective C classes or other @objc classes. They can’t be adopted by structures or enumerations.

- An optional protocol requirement can be called with **optional chaining**.

example :

``` swift
@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

class TowardsZeroSource: NSObject, CounterDataSource {
    func increment(forCount count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}
```

``` swift
class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

var counter = Counter()
counter.dataSource = ThreeSource()
counter.increment(). // 3
counter.dataSource = TowardsZeroSource()
counter.increment(). // 2
```
---

### Protocol Extensions

- Protocols can be extended to provide method, initializer, subscript, and computed property implementations to conforming types.

example :

``` swift
protocol RandomNumberGenerator {
    func random() -> Double
}

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}
```

- Protocol extensions can add implementations to conforming types but can’t make a protocol extend or inherit from another protocol.

- Constraints can be specified for the conforming types to satisfy before the methods and properties of the extension are available using **generic** where clause. 

example : 

``` swift
extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}
```

If a conforming type satisfies the requirements for multiple constrained extensions that provide implementations for the same method or property, Swift uses the implementation corresponding to the most specialized constraints.