Протоколы в Swift

Протоколы определяют методы и свойства, необходимые для реализации определённой функциональности.

Считается, что любой тип, удовлетворяющий требованиям протокола, соответствует протоколу.

Классы, структуры или типы перечислений могут все соответствовать протоколам и предоставлять конкретные реализации для выполнения методов и функциональности, определённых протоколом.

Протоколы Swift — это шаблоны, которые определяют методы, свойства и другие требования, подходящие для конкретной задачи или возможности. Протоколы могут быть приняты классами, структурами и перечислениями для предоставления конкретных реализаций этих требований.

Протоколы — это очень мощная возможность языка Swift, позволяющая разработчикам определять интерфейсы, задавая свойства и методы, которые должны быть у типа, без указания конкретных реализаций этих свойств и методов.

Синтаксис

Синтаксис протокола выглядит следующим образом:

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

SomeProtocol определяет доступное для чтения и записи свойство someProperty и метод someMethod.

Чтобы сделать класс соответствующим протоколу, добавьте имя протокола после имени типа, отделив его двоеточием:как часть определения типа. При соответствии нескольким протоколам разделяйте каждый протокол запятой,.

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

Если у класса есть суперкласс и он соответствует протоколу, укажите имя суперкласса перед именем протокола, разделив их запятой.

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

Требования к свойствам

Протоколы используются для указания конкретных свойств экземпляра или свойств типа, без указания того, являются ли они хранимыми или вычисляемыми свойствами. Кроме того, необходимо указать, доступны ли они только для чтения или для чтения и записи.

В протоколах обычно используется var для объявления переменных свойств. После объявления типа добавьте{ set get }, чтобы указать, что свойство доступно для чтения и записи. Для свойств, доступных только для чтения, используйте{ get }чтобы указать.

Примеры

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 "У \(stname) 99% посещаемости"
    }
   
    func markssecured() -> String {
        return "\(stname) набрал \(marks) баллов"
    }
}

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

print(studdet.markssecured())
print(studdet.attendance())
print("Баллы: \(studdet.marks)")
print("Результат: \(studdet.result)")
print("Присутствие: \(studdet.present)")
print("Предмет: \(studdet.subject)")
print("Имя студента: \(studdet.stname)")

Вывод выполнения вышеуказанной программы:

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

Требования к mutating-методам

Иногда необходимо изменить экземпляр внутри его метода.

Например, в методах экземпляра типов значений (структур, перечислений) добавьте ключевое слово mutating перед func — это указывает, что методу разрешено изменять экземпляр, которому он принадлежит, и значения его свойств экземпляра.

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()

Вывод выполнения вышеуказанной программы:

Wednesday

Требования к инициализаторам

Протоколы могут требовать, чтобы типы, соответствующие протоколу, реализовывали определённые инициализаторы.

Вы можете объявлять инициализаторы в определении протокола так же, как обычные инициализаторы, но без фигурных скобок и тела инициализатора. Синтаксис следующий:

protocol SomeProtocol {
   init(someParameter: Int)
}

Примеры

protocol tcpprotocol {
   init(aprot: Int)
}

Реализация требований протокола к инициализаторам в классах

Вы можете реализовать инициализатор в классе, соответствующем протоколу, и определить его как назначенный инициализатор или вспомогательный инициализатор класса. В обоих случаях необходимо пометить реализацию инициализатора модификатором "required":

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

protocol tcpprotocol {
   init(aprot: Int)
}

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

Использование модификатора required гарантирует, что все подклассы, соответствующие протоколу, также предоставляют явную реализацию или наследуют реализацию требования к инициализатору.

Если подкласс переопределяет назначенный инициализатор из своего суперкласса, и этот инициализатор удовлетворяет требованию протокола, реализация инициализатора должна быть помечена обоими модификаторами — required и override:

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)")

Вывод выполнения вышеуказанной программы:

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

Протоколы как типы

Хотя протоколы сами по себе не реализуют какую-либо функциональность, они могут использоваться как типы.

Протоколы можно использовать как любой другой обычный тип. Варианты использования:

  • В качестве типов параметров или возвращаемых типов в функциях, методах или инициализаторах
  • В качестве типа констант, переменных или свойств
  • В качестве типов элементов в массивах, словарях или других контейнерах

Примеры

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 }))

Вывод выполнения вышеуказанной программы:

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

Добавление членов протокола с помощью расширений

Мы можем использовать расширения для расширения существующих типов (классов, структур, перечислений и т. д.).

Расширения могут добавлять такие члены, как свойства, методы, индексы, протоколы и т. д., к существующим типам.

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())")

Вывод вышеприведённого кода:

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

Наследование протоколов

Протокол может наследовать один или несколько других протоколов и может добавлять дополнительные требования поверх унаследованных протоколов.

Синтаксис наследования протоколов похож на наследование классов. Несколько наследуемых протоколов разделяются запятыми:

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

Примеры

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)

Вывод выполнения вышеуказанной программы:

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

Протоколы только для классов

Вы можете ограничить протокол только типами классов, добавив ключевое слово class в его список наследования.

Ключевое слово class должно быть первым элементом в списке наследования протокола, за ним следуют другие наследуемые протоколы. Формат следующий:

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

Примеры

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)")

Вывод выполнения вышеуказанной программы:

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

Композиция протоколов

Swift поддерживает композицию нескольких протоколов, что очень полезно, когда нам нужно одновременно соответствовать нескольким протоколам.

Формат синтаксиса следующий:

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)

Вывод выполнения вышеуказанной программы:

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

Проверка соответствия протоколу

Вы можете использовать операторы is и as, чтобы проверить, соответствует ли экземпляр протоколу, или привести его к определённому типу.

  • isОператор используется для проверки того, соответствует ли экземплярFollowопределённомуProtocol。
  • as?возвращает опциональное значение. Когда экземплярFollowсоответствует протоколу, он возвращает тип протокола; в противном случае возвращает nil.nil。
  • asиспользуется для принудительного понижающего приведения. Если приведение не удаётся, возникает ошибка времени выполнения.

Примеры

В следующем примере определяется протокол с именем HasArea, который требует доступное для чтения свойство area типа 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("没有面积")
    }
}

Вывод выполнения вышеуказанной программы:

面积为 12.5663708
面积为 243610.0
没有面积
Другие расширения