Protocols are extremely important in Swift, and can be used in any project development.

I. Definition of Protocol

Protocols can be used to define declarations of methods, properties, and subscripts. Protocols can be followed by enumerations, structures, and classes (multiple protocols separated by commas).

Sample code:

Protocol Drawable {var x: Int {get set} var y: Int {get} func draw() subscript(index: index) Drawable {var x: Int {get set} var y: Int {get} func draw() subscript(index: index) Int) -> Int {get set}} protocol Test1 {} protocol Test2 {} protocol Test3 {} Test1, Test2, Test3, Drawable { // do something... }Copy the code

Features:

  • Methods defined in protocols cannot have default parameter values.
  • By default, everything defined in the protocol must be implemented.

Properties in the protocol

The var keyword must be used to define attributes in the protocol.

Sample code:

protocol Drawable {
    var x: Int { get set }
    var y: Int { get }
    func draw()
    subscript(index: Int) -> Int { get set }
}
Copy the code

The protocol properties X and Y in the example code above are not meant to be computed properties, they are meant only to be readable/writable/read-write.

Implementation of protocol attributes: When implementing the protocol, the attribute permissions shall be no less than those defined in the protocol:

  • Protocol definitionget,setwithvarStore attributes orget,setCompute properties to implement;
  • Protocol definitionget, can be implemented with any attribute.

Scene 1:

Class Person: Drawable {var x: Int = 0; Int = 0 func draw() { print("Person draw") } subscript(index: Int) -> Int { set { } get { index } } }Copy the code

Scene 2:

Class Person: Drawable {var x: Int {set {} get {0}} var x: Int {set {} get {0}} Int { 0 } func draw() { print("Person draw") } subscript(index: Int) -> Int { set { } get { index } } }Copy the code

Static, class, mutating, init

3.1. The static, class

To ensure generality, the protocol must use static to define type methods, type attributes, and type subscripts.

Sample code:

protocol Drawable {
    static func draw()
}
Copy the code

The protocol can be implemented using either static or class, depending on whether subclasses need to be overridden.

class Person1: Drawable {
    static func draw() {
        print("Person1 draw")
    }
}

class Person2: Drawable {
    class func draw() {
        print("Person2 draw")
    }
}
Copy the code

3.2. mutating

The implementation of a structure or enumeration is allowed to modify its own memory only if the instance method in the protocol is marked mutating.

Classes that implement methods without mutating need to mutate enumerations or structs.

Sample code:

protocol Drawable {
    mutating func draw()
}

class Size: Drawable {
    var width: Int = 0
    func draw() {
        width = 10
    }
}

struct Point: Drawable {
    var x: Int = 0
    mutating func draw() {
        x = 10
    }
}
Copy the code

If the protocol does not include mutating, the class attribute value is not affected. However, the value type cannot be mutating when implementing the protocol function, otherwise an error will be reported.

3.3. The init

  1. Initializers can also be defined in the protocolinit,finalClass implementation must addrequired.

Sample code:

protocol Drawable {
    init(x: Int, y: Int)
}

class Point: Drawable {
    required init(x: Int, y: Int) {
        
    }
}

final class Size: Drawable {
    init(x: Int, y: Int) {
        
    }
}
Copy the code

Consider: Why does the protocol restrict non-final classes to be required? Because the protocol definitely expects the defined initializer to be implemented by both the compliant class and its subclasses, required is added, and the compliant subclass must also implement the protocol. Classes with final cannot be inherited, so there is no need to add required.

  1. If the initializer implemented from the protocol happens to override the specified initializer of the parent class, then this initializer must also be addedrequired,override.

Sample code:

protocol Animal {
    init(age: Int)
}

class Person {
    init(age: Int) { }
}

class Student: Person, Animal {
    required override init(age: Int) {
        super.init(age: age)
    }
}
Copy the code

Note: Subclasses override the parent’s required initializer. Subclasses do not override, regardless of protocol compliance. In the example above, required represents compliance with the protocol, and Override represents overriding the parent class.

Init, init? And init! The use of:

  • As defined in the protocolinit?,init!, you can useinit,init?,init!To achieve;
  • As defined in the protocolinit, you can useinit,init!To achieve them.

Sample code:

protocol Animal { init() init? (age: Int) init! (height: Int) } class Person: Animal { required init() { } // required init! () { } required init? (age: Int) { } // required init! (age: Int) { } // required init(age: Int) { } required init! (height: Int) { } // required init? (height: Int) { } // required init(height: Int) { } }Copy the code

Note: In inheritance relationships, it is possible to rewrite a failable initializer with a non-failable initializer, but not the other way around. But protocol initializers can be implemented with failable initializers that can and can only be unpacked implicitly.

Inheritance and combination of protocols

4.1. Protocol inheritance

A protocol can inherit (or abide by) other protocols.

Sample code:

protocol Runnable {
    func run()
}

protocol Livable: Runnable {
    func breath()
}

class Person: Livable {
    func breath() { }
    func run() { }
}
Copy the code

4.2. Protocol Combination

  • A protocol combination can contain up to one class type
  • For multiple protocols and class types&The connection

Sample code:

Protocol Runnable {} protocol Livable: Runnable {} class Person: Func fn0(obj: Person) {} // To receive instances of Person or subclass func fn0(obj: Person) {} // to receive instances of Person obeying the Livable protocol func fn1(obj: Livable) {} // receive instances of Livable, Runnable protocols func fn2(obj: Func fn3(obj: Person & Livable & Runnable) {} func fn3(obj: Person & Livable & Runnable) {}Copy the code

In the example code above, fn2 and FN3 are protocol combinations.

Fn3 can also use the following methods:

typealias RealPerson = Person & Livable & Runnable
func fn4(obj: RealPerson) { }
Copy the code

5. Common protocols

5.1. CaseIterable protocol (enumeration iterators)

Making enumerations conform to the CaseIterable protocol allows you to iterate over an enumeration value.

Sample code:

enum Season: CaseIterable { case spring, summer, autumn, winter } let seasons = Season.allCases print(type(of: // output: Array<Season> print(seasons. Count) // output: 4 for Season in seasons {print(Season)} /* output:  spring summer autumn winter */Copy the code

CaseIterable provides an allCases type attribute that returns an array containing all the values of the enumeration.

Let seasons = [Season. Spring, Season. Summer, Season. Autumn, Season. Winter]

5.2. CustomStringConvertible/CustomDebugStringConvertible

Keep CustomStringConvertible, CustomDebugStringConvertible agreement, can be custom print string instance.

Sample code:

class Person: CustomStringConvertible, CustomDebugStringConvertible { var age = 0 var description: String { "person_\(age)" } var debugDescription: String {"debug_person_\(age)"}} var p = Person() print(p) // Debug_person_0Copy the code

  • printCall isCustomStringConvertibleOf the agreementdescription
  • debugPrint,poCall isCustomDebugStringConvertibleOf the agreementdebugDescription