Протоколы в 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 }чтобы указать.
Примеры
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 没有面积Другие расширения