Swift Protocols

Protocols define the methods and properties required to implement a particular functionality.

Any type that satisfies the requirements of a protocol is said to conform to the protocol.

Classes, structs, or enum types can all conform to protocols and provide concrete implementations to fulfill the methods and functionality defined by the protocol.

Swift protocols are blueprints that define methods, properties, and other requirements suited to a particular task or feature. Protocols can be adopted by classes, structs, and enums to provide concrete implementations of these requirements.

Protocols are a very powerful feature in the Swift language that allow developers to define interfaces, specifying the properties and methods a type must have, without specifying the concrete implementations of those properties and methods.

Syntax

The syntax format of a protocol is as follows:

protocol SomeProtocol {
    // 这里可以定义协议要求
    var someProperty: Int { get set }
    func someMethod()
}

SomeProtocol defines a readable and writable property someProperty and a method someMethod.

To make a class conform to a protocol, add the protocol name after the type name, separated by a colon:as part of the type definition. When conforming to multiple protocols, separate each protocol with a comma,separated.

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 结构体内容
}

If a class has a superclass while conforming to a protocol, place the superclass name before the protocol name, separated by a comma.

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 类的内容
}

Property Requirements

Protocols are used to specify particular instance properties or type properties, without specifying whether they are stored or computed properties. In addition, you must indicate whether they are read-only or readable and writable.

In protocols, var is usually used to declare variable properties. After the type declaration, add{ set get }to indicate the property is readable and writable. For read-only properties, use{ get }to indicate.

Examples

protocol ClassA {
    var marks: Int { get set }
    var result: Bool { get }
   
    func attendance() -> String
    func markssecured() -> String
}

protocol ClassB: ClassA {
    var present: Bool { get set }
    var subject: String { get set }
    var stname: String { get set }
}

class ClassC: ClassB {
    var marks = 96
    let result = true
    var present = false
    var subject = "Swift Protocols"
    var stname = "Protocols"
   
    func attendance() -> String {
        return "The \(stname) has secured 99% attendance"
    }
   
    func markssecured() -> String {
        return "\(stname) has scored \(marks) marks"
    }
}

let studdet = ClassC()
studdet.stname = "Swift"
studdet.marks = 98

print(studdet.markssecured())
print(studdet.attendance())
print("Marks: \(studdet.marks)")
print("Result: \(studdet.result)")
print("Present: \(studdet.present)")
print("Subject: \(studdet.subject)")
print("Student Name: \(studdet.stname)")

The output of the above program execution is:

Swift has scored 98 marks
The Swift has secured 99% attendance
Marks: 98
Result: true
Present: false
Subject: Swift 协议
Student Name: Swift

Mutating Method Requirements

Sometimes you need to modify an instance within its method.

For example, in instance methods of value types (structs, enums), prefix the function with the mutating keyword before func, indicating that the method is allowed to modify the instance it belongs to and the values of its instance properties.

protocol Daysofaweek {
    mutating func show()
}

enum Days: Daysofaweek {
    case sun, mon, tue, wed, thurs, fri, sat

    mutating func show() {
        switch self {
        case .sun:
            self = .sun
            print("Sunday")
        case .mon:
            self = .mon
            print("Monday")
        case .tue:
            self = .tue
            print("Tuesday")
        case .wed:
            self = .wed
            print("Wednesday")
        case .thurs:
            self = .thurs
            print("Thursday")
        case .fri:
            self = .fri
            print("Friday")
        case .sat:
            self = .sat
            print("Saturday")
        }
    }
}

var res = Days.wed
res.show()

The output of the above program execution is:

Wednesday

Initializer Requirements

Protocols can require conforming types to implement specific initializers.

You can write initializer declarations in a protocol definition just like writing ordinary initializers, but without curly braces and the initializer body. The syntax is as follows:

protocol SomeProtocol {
   init(someParameter: Int)
}

Examples

protocol tcpprotocol {
   init(aprot: Int)
}

Implementing Protocol Initializer Requirements in Classes

You can implement the initializer in a class that conforms to the protocol, and designate it as either a designated initializer or a convenience initializer for the class. In both cases, you must mark the initializer implementation with the "required" modifier:

class SomeClass: SomeProtocol {
   required init(someParameter: Int) {
      // 构造器实现
   }
}

protocol tcpprotocol {
   init(aprot: Int)
}

class tcpClass: tcpprotocol {
   required init(aprot: Int) {
   }
}

Using the required modifier ensures that all subclasses that conform to the protocol also provide an explicit implementation or inherit an implementation for the initializer requirement.

If a subclass overrides a designated initializer from its superclass, and that initializer satisfies a protocol requirement, the initializer implementation must be marked with both required and override modifiers:

protocol TcpProtocol {
    init(no1: Int)
}

class MainClass {
    var no1: Int // 局部变量
    init(no1: Int) {
        self.no1 = no1 // 初始化
    }
}

class SubClass: MainClass, TcpProtocol {
    var no2: Int
    
    init(no1: Int, no2: Int) {
        self.no2 = no2
        super.init(no1: no1)
    }
    
    // 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override"
    required override convenience init(no1: Int) {
        self.init(no1: no1, no2: 0)
    }
}

let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)

print("res is: \(res.no1)")
print("show.no1 is: \(show.no1)")
print("show.no2 is: \(show.no2)")

The output of the above program execution is:

res is: 20
show.no1 is: 30
show.no2 is: 50

Protocols as Types

Even though protocols do not implement any functionality themselves, they can be used as types.

Protocols can be used like any other ordinary type. Use cases:

  • As parameter types or return types in functions, methods, or initializers
  • As the type of constants, variables, or properties
  • As element types in arrays, dictionaries, or other containers

Examples

protocol Generator {
    associatedtype Members
    func next() -> Members?
}

// 示例代码使用 Swift 标准库中的迭代器和 `map` 函数

// 使用数组的迭代器
var items = [10, 20, 30].makeIterator()
while let x = items.next() {
    print(x)
}

// 使用 `map` 函数
for list in [1, 2, 3].map({ i in i * 5 }) {
    print(list)
}

// 直接打印数组
print([100, 200, 300])
print([1, 2, 3].map({ i in i * 10 }))

The output of the above program execution is:

10
20
30
5
10
15
[100, 200, 300]
[10, 20, 30]

Adding Protocol Members with Extensions

We can use extensions to extend existing types (classes, structs, enums, etc.).

Extensions can add members such as properties, methods, subscripts, protocols, etc. to existing types.

protocol AgeClassificationProtocol {
    var age: Int { get }
    func agetype() -> String
}

class Person {
    let firstname: String
    let lastname: String
    var age: Int

    init(firstname: String, lastname: String, age: Int = 10) {
        self.firstname = firstname
        self.lastname = lastname
        self.age = age
    }
}

extension Person: AgeClassificationProtocol {
    func fullname() -> String {
        return "\(firstname) \(lastname)"
    }

    func agetype() -> String {
        switch age {
        case 0...2:
            return "Baby"
        case 3...12: // 修正为从3开始
            return "Child"
        case 13...19:
            return "Teenager"
        case let x where x > 65:
            return "Elderly"
        default:
            return "Normal"
        }
    }
}

// 测试代码
let person1 = Person(firstname: "John", lastname: "Doe", age: 25)
let person2 = Person(firstname: "Jane", lastname: "Smith", age: 70)
let person3 = Person(firstname: "Baby", lastname: "Yoda", age: 1)

print("\(person1.fullname()) is a \(person1.agetype())")
print("\(person2.fullname()) is a \(person2.agetype())")
print("\(person3.fullname()) is a \(person3.agetype())")

The output of the above code is:

John Doe is a Normal
Jane Smith is a Elderly
Baby Yoda is a Baby

Protocol Inheritance

A protocol can inherit one or more other protocols, and can add further requirements on top of the inherited protocols.

The syntax for protocol inheritance is similar to class inheritance. Multiple inherited protocols are separated by commas:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 协议定义
}

Examples

protocol Classa {
    var no1: Int { get set }
    func calc(sum: Int)
}

protocol Result {
    func print(target: Classa)
}

class Student2: Result {
    func print(target: Classa) {
        target.calc(sum: 1)
    }
}

class Classb: Result {
    func print(target: Classa) {
        target.calc(sum: 5)
    }
}

class Student: Classa {
    var no1: Int = 10
    
    func calc(sum: Int) {
        no1 -= sum
        print("学生尝试 \(sum) 次通过")
        
        if no1 <= 0 {
            print("学生缺席考试")
        }
    }
}

class Player {
    var stmark: Result!
    
    init(stmark: Result) {
        self.stmark = stmark
    }
    
    func print(target: Classa) {
        stmark.print(target: target)
    }
}

var marks = Player(stmark: Student2())
var marksec = Student()

marks.print(target: marksec)
marks.print(target: marksec)
marks.print(target: marksec)
marks.stmark = Classb()
marks.print(target: marksec)
marks.print(target: marksec)
marks.print(target: marksec)

The output of the above program execution is:

学生尝试 1 次通过
学生尝试 1 次通过
学生尝试 1 次通过
学生尝试 5 次通过
学生尝试 5 次通过
学生缺席考试
学生尝试 5 次通过
学生缺席考试

Class-Only Protocols

You can restrict a protocol to class types only by adding the class keyword to its inheritance list.

The class keyword must be the first item in the protocol's inheritance list, followed by the other inherited protocols. The format is as follows:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 协议定义
}

Examples

protocol TcpProtocol {
    init(no1: Int)
}

class MainClass {
    var no1: Int // 局部变量
    init(no1: Int) {
        self.no1 = no1 // 初始化
    }
}

class SubClass: MainClass, TcpProtocol {
    var no2: Int

    init(no1: Int, no2: Int) {
        self.no2 = no2
        super.init(no1: no1)
    }
    
    // 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override"
    required override convenience init(no1: Int) {
        self.init(no1: no1, no2: 0)
    }
}

let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)

print("res is: \(res.no1)")
print("show.no1 is: \(show.no1)")
print("show.no2 is: \(show.no2)")

The output of the above program execution is:

res is: 20
show.no1 is: 30
show.no2 is: 50

Protocol Composition

Swift supports composing multiple protocols, which is very useful when we need to conform to multiple protocols at the same time.

The syntax format is as follows:

protocol Stname {
    var name: String { get }
}

protocol Stage {
    var age: Int { get }
}

struct Person: Stname, Stage {
    var name: String
    var age: Int
}

func show(celebrator: Stname & Stage) {
    print("\(celebrator.name) is \(celebrator.age) years old")
}

let studname = Person(name: "Priya", age: 21)
show(celebrator: studname)

let stud = Person(name: "Rehan", age: 29)
show(celebrator: stud)

let student = Person(name: "Roshan", age: 19)
show(celebrator: student)

The output of the above program execution is:

Priya is 21 years old
Rehan is 29 years old
Roshan is 19 years old

Checking for Protocol Conformance

You can use the is and as operators to check whether an instance conforms to a protocol or to cast it to a certain type.

  • isThe operator is used to check whether an instanceFollowconforms to a certainProtocol。
  • as?returns an optional value. When the instanceFollowconforms to the protocol, it returns the protocol type; otherwise, it returns nil.nil。
  • asis used for forced downcasting. If the cast fails, it causes a runtime error.

Examples

The following example defines a protocol called HasArea, which requires a readable area of type Double:

protocol HasArea {
    var area: Double { get }
}

// 定义了Circle类,都遵循了HasArea协议
class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return pi * radius * radius }
    init(radius: Double) { self.radius = radius }
}

// 定义了Country类,都遵循了HasArea协议
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}

// Animal是一个没有实现HasArea协议的类
class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]

for object in objects {
    // 对迭代出的每一个元素进行检查,看它是否遵循了HasArea协议
    if let objectWithArea = object as? HasArea {
        print("面积为 \(objectWithArea.area)")
    } else {
        print("没有面积")
    }
}

The output of the above program execution is:

面积为 12.5663708
面积为 243610.0
没有面积
Other Extensions