Protocolos Swift
Un protocolo define los métodos y propiedades necesarios para implementar una funcionalidad específica.
Cualquier tipo que cumpla con los requisitos del protocolo se denomina conforme (conform) a este protocolo.
Las clases, estructuras o enumeraciones pueden adoptar el protocolo y proporcionar una implementación concreta para completar los métodos y funcionalidades definidos por el protocolo.
Los protocolos Swift son planos que definen métodos, propiedades y otros requisitos, adecuados para una tarea o funcionalidad específica. Los protocolos pueden ser adoptados por clases, estructuras y enumeraciones, y proporcionan implementaciones concretas de estos requisitos.
Los protocolos son una característica muy potente del lenguaje Swift. Permiten a los desarrolladores definir interfaces, especificando las propiedades y métodos que debe tener un tipo, sin necesidad de especificar la implementación concreta de dichas propiedades y métodos.
Sintaxis
La sintaxis del protocolo es la siguiente:
protocol SomeProtocol {
// 这里可以定义协议要求
var someProperty: Int { get set }
func someMethod()
}
SomeProtocol define una propiedad someProperty de lectura y escritura, y un método someMethod.
Para que una clase siga un protocolo, se debe añadir el nombre del protocolo después del nombre del tipo, separados por dos puntos:como parte de la definición del tipo. Cuando se adoptan varios protocolos, se utiliza una coma,para separarlos.
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 结构体内容
}
Si una clase tiene una superclase además de adoptar el protocolo, el nombre de la superclase debe colocarse antes del nombre del protocolo, separados por comas.
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
}
Requisitos de propiedades
El protocolo se utiliza para especificar propiedades de instancia o de clase concretas, sin especificar si son propiedades almacenadas o computadas. Además, debe indicarse si son de solo lectura o de lectura y escritura.
En el protocolo, generalmente se usa `var` para declarar propiedades variables; después de la declaración del tipo, se añade{ set get }para indicar que la propiedad es de lectura y escritura; para las de solo lectura se usa{ get }para indicarlo.
Ejemplo
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 = "Protocolos Swift"
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)")
El resultado de ejecutar el programa anterior es:
Swift has scored 98 marks The Swift has secured 99% attendance Marks: 98 Result: true Present: false Subject: Swift 协议 Student Name: Swift
Requisitos para métodos mutating
A veces es necesario modificar la propia instancia dentro de un método.
Por ejemplo, en los métodos de instancia de los tipos de valor (estructuras, enumeraciones), se coloca la palabra clave `mutating` como prefijo de la función, delante de `func`, para indicar que se puede modificar la instancia a la que pertenece y el valor de sus propiedades de instancia dentro de ese método.
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()
El resultado de ejecutar el programa anterior es:
Wednesday
Requisitos para inicializadores
Un protocolo puede exigir que sus conformadores implementen un inicializador especificado.
Puedes escribir la declaración del inicializador en la definición del protocolo igual que se escribe un inicializador normal, pero sin necesidad de escribir llaves ni el cuerpo del inicializador. La sintaxis es la siguiente:
protocol SomeProtocol {
init(someParameter: Int)
}
Ejemplo
protocol tcpprotocol {
init(aprot: Int)
}
Implementación de los requisitos de inicializadores del protocolo en clases
Puedes implementar el inicializador en una clase que siga el protocolo y designarlo como inicializador designado o inicializador de conveniencia. En ambos casos, debes marcar la implementación del inicializador con el modificador `required`:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 构造器实现
}
}
protocol tcpprotocol {
init(aprot: Int)
}
class tcpClass: tcpprotocol {
required init(aprot: Int) {
}
}
El uso del modificador `required` garantiza que todas las subclases que sigan el protocolo proporcionen una implementación explícita o heredada para el requisito del inicializador.
Si una subclase sobrescribe el inicializador designado de la superclase y dicho inicializador cumple con un requisito del protocolo, la implementación debe marcarse simultáneamente con los modificadores `required` y `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)")
El resultado de ejecutar el programa anterior es:
res is: 20 show.no1 is: 30 show.no2 is: 50
Tipos de protocolo
Aunque el protocolo en sí no implementa ninguna funcionalidad, puede utilizarse como un tipo.
Los protocolos se pueden usar como cualquier otro tipo normal. Escenarios de uso:
- Como tipo de parámetro o de retorno en funciones, métodos o inicializadores.
- Como tipo de constantes, variables o propiedades.
- Como tipo de elemento en arreglos, diccionarios u otros contenedores.
Ejemplo
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 }))
El resultado de ejecutar el programa anterior es:
10 20 30 5 10 15 [100, 200, 300] [10, 20, 30]
Agregar miembros del protocolo en extensiones
Podemos ampliar tipos existentes (clases, estructuras, enumeraciones, etc.) mediante extensiones.
Las extensiones pueden agregar a los tipos existentes miembros como propiedades, métodos, subíndices y conformidades de protocolo.
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())")
El resultado del código anterior es:
John Doe is a Normal Jane Smith is a Elderly Baby Yoda is a Baby
Herencia de protocolos
Un protocolo puede heredar de uno o más protocolos, y puede agregar nuevos requisitos además de los protocolos heredados.
La sintaxis de herencia de protocolos es similar a la herencia de clases; los protocolos heredados se separan con comas:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 协议定义
}
Ejemplo
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)
El resultado de ejecutar el programa anterior es:
学生尝试 1 次通过 学生尝试 1 次通过 学生尝试 1 次通过 学生尝试 5 次通过 学生尝试 5 次通过 学生缺席考试 学生尝试 5 次通过 学生缺席考试
Protocolos exclusivos para clases
Puedes restringir que un protocolo solo se adapte a tipos de clase agregando la palabra clave `class` en su lista de herencia.
La palabra clave `class` debe ser la primera en la lista de herencia del protocolo; después van los demás protocolos heredados. El formato es el siguiente:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 协议定义
}
Ejemplo
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)")
El resultado de ejecutar el programa anterior es:
res is: 20 show.no1 is: 30 show.no2 is: 50
Composición de protocolos
Swift admite la composición de varios protocolos, lo cual es muy útil cuando necesitamos adoptar varios protocolos al mismo tiempo.
La sintaxis es la siguiente:
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)
El resultado de ejecutar el programa anterior es:
Priya is 21 years old Rehan is 29 years old Roshan is 19 years old
Comprobación de la conformidad con protocolos
Puedes usar los operadores `is` y `as` para comprobar si se sigue un protocolo o forzar la conversión a un tipo.
isEl operador se utiliza para comprobar si una instanciaseguirha adoptado algúnprotocolo。as?Devuelve un valor opcional; cuando la instanciaseguires conforme al protocolo, devuelve el tipo del protocolo; de lo contrario, devuelvenil。asse utiliza para forzar la conversión descendente; si la conversión falla, se producirá un error en tiempo de ejecución.
Ejemplo
El siguiente ejemplo define un protocolo HasArea que requiere una propiedad `area` de tipo Double de solo lectura:
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("没有面积")
}
}
El resultado de ejecutar el programa anterior es:
面积为 12.5663708 面积为 243610.0 没有面积Otras extensiones