DEV Community

Naveen Ragul B
Naveen Ragul B

Posted on • Updated on

Swift - Protocol

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 :

protocol someProtocol{
    var property : Int { get }
}

class someClass : someProtocol{
    let property : Int

    init( _ property : Int){
        self.property = property
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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 :

protocol RandomNumberGenerator {
    func random() -> Double
}
Enter fullscreen mode Exit fullscreen mode

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 :

protocol Togglable {
    mutating func toggle()
}
Enter fullscreen mode Exit fullscreen mode
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()
Enter fullscreen mode Exit fullscreen mode
  • 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 :

protocol SomeProtocol {
    init()
}

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

class SomeSubClass: SomeSuperClass, SomeProtocol {
    required override init() {
        // initializer implementation goes here
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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 :

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())
Enter fullscreen mode Exit fullscreen mode
protocol TextRepresentable {
    var textualDescription: String { get }
}

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

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
Enter fullscreen mode Exit fullscreen mode
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]"

Enter fullscreen mode Exit fullscreen mode

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 :

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}
Enter fullscreen mode Exit fullscreen mode

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 :

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}
Enter fullscreen mode Exit fullscreen mode
  • Protocol Composition - multiple protocols can be combined into a single requirement by separating them with ampersands (&).

example :

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)!")
}
Enter fullscreen mode Exit fullscreen mode

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 :

@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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

Protocol Extensions

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

example :

protocol RandomNumberGenerator {
    func random() -> Double
}

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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 :

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

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.

Top comments (0)