Buscar..


Introducción

Los protocolos son una forma de especificar cómo usar un objeto. Describen un conjunto de propiedades y métodos que una clase, estructura o enumeración deberían proporcionar, aunque los protocolos no imponen restricciones a la implementación.

Observaciones

Un protocolo Swift es una colección de requisitos que los tipos conformes deben implementar. El protocolo se puede usar en la mayoría de los lugares donde se espera un tipo, por ejemplo, matrices y requisitos genéricos.

Los miembros del protocolo siempre comparten el mismo calificador de acceso que todo el protocolo y no se pueden especificar por separado. Aunque un protocolo podría restringir el acceso con los requisitos de getter o setter, como se muestra en los ejemplos anteriores.

Para obtener más información sobre los protocolos, consulte El lenguaje de programación Swift .

Los protocolos Objective-C son similares a los protocolos Swift.

Los protocolos también son comparables a las interfaces de Java .

Conceptos básicos del protocolo

Acerca de los protocolos

Un protocolo especifica inicializadores, propiedades, funciones, subíndices y tipos asociados requeridos de un tipo de objeto Swift (clase, estructura o enumeración) conforme al protocolo. En algunos idiomas, ideas similares para especificaciones de requisitos de objetos subsiguientes se conocen como 'interfaces'.

Un protocolo declarado y definido es un tipo, en sí mismo, con una firma de sus requisitos establecidos, algo similar a la manera en que las funciones Swift son un tipo basado en su firma de parámetros y devoluciones.

Las especificaciones de Swift Protocol pueden ser opcionales, explícitamente requeridas y / o dadas implementaciones predeterminadas a través de una instalación conocida como Protocol Extensions. Un tipo de objeto Swift (clase, estructura o enumeración) que desee ajustarse a un Protocolo que se completa con Extensions para todos sus requisitos específicos solo debe indicar su deseo de cumplir con la conformidad total. La facilidad de implementación predeterminada de las Extensiones de Protocolo puede ser suficiente para cumplir con todas las obligaciones de conformidad con un Protocolo.

Los Protocolos pueden ser heredados por otros Protocolos. Esto, junto con las Extensiones de protocolo, significa que los Protocolos pueden y deben considerarse como una característica importante de Swift.

Los protocolos y extensiones son importantes para realizar los objetivos y enfoques más amplios de Swift para la flexibilidad del diseño de programas y los procesos de desarrollo. El propósito principal declarado de la capacidad de Protocolo y Extensión de Swift es facilitar el diseño compositivo en la arquitectura y el desarrollo del programa. Esto se conoce como Programación Orientada al Protocolo. Los viejos crustadores consideran esto superior a un enfoque en el diseño OOP.

Los protocolos definen interfaces que pueden ser implementadas por cualquier estructura , clase o enumeración :

protocol MyProtocol {
    init(value: Int)                      // required initializer
    func doSomething() -> Bool            // instance method
    var message: String { get }           // instance read-only property
    var value: Int { get set }            // read-write instance property
    subscript(index: Int) -> Int { get }  // instance subscript
    static func instructions() -> String  // static method
    static var max: Int { get }           // static read-only property
    static var total: Int { get set }     // read-write static property
}

Las propiedades definidas en los protocolos deben anotarse como { get } o { get set } . { get } significa que la propiedad debe ser obtenible, y por lo tanto, se puede implementar como cualquier tipo de propiedad. { get set } significa que la propiedad debe ser configurable así como también gettable.

Una estructura, clase o enumeración puede ajustarse a un protocolo:

struct MyStruct : MyProtocol {
    // Implement the protocol's requirements here
}
class MyClass : MyProtocol {
    // Implement the protocol's requirements here
}
enum MyEnum : MyProtocol {
    case caseA, caseB, caseC
    // Implement the protocol's requirements here
}

Un protocolo también puede definir una implementación predeterminada para cualquiera de sus requisitos a través de una extensión :

extension MyProtocol {
    
    // default implementation of doSomething() -> Bool
    // conforming types will use this implementation if they don't define their own
    func doSomething() -> Bool {
        print("do something!")
        return true
    }
}

Se puede usar un protocolo como tipo , siempre que no tenga requisitos de tipo associatedtype :

func doStuff(object: MyProtocol) {
    // All of MyProtocol's requirements are available on the object
    print(object.message)
    print(object.doSomething())
}

let items : [MyProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]

También puede definir un tipo abstracto que se ajuste a múltiples protocolos:

3.0

Con Swift 3 o superior, esto se hace separando la lista de protocolos con un signo ( & ):

func doStuff(object: MyProtocol & AnotherProtocol) {
    // ...
}

let items : [MyProtocol & AnotherProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]
3.0

Las versiones anteriores tienen un protocol<...> sintaxis protocol<...> donde los protocolos son una lista separada por comas entre los corchetes angulares <> .

protocol AnotherProtocol {
    func doSomethingElse()
}

func doStuff(object: protocol<MyProtocol, AnotherProtocol>) {
    
    // All of MyProtocol & AnotherProtocol's requirements are available on the object
    print(object.message)
    object.doSomethingElse()
}

// MyStruct, MyClass & MyEnum must now conform to both MyProtocol & AnotherProtocol
let items : [protocol<MyProtocol, AnotherProtocol>] = [MyStruct(), MyClass(), MyEnum.caseA]

Los tipos existentes se pueden ampliar para ajustarse a un protocolo:

extension String : MyProtocol {
    // Implement any requirements which String doesn't already satisfy
}

Requisitos de tipo asociado

Los protocolos pueden definir los requisitos de tipo asociados utilizando la palabra clave de tipo associatedtype :

protocol Container {
    associatedtype Element
    var count: Int { get }
    subscript(index: Int) -> Element { get set }
}

Los protocolos con requisitos de tipo asociados solo pueden usarse como restricciones genéricas :

// These are NOT allowed, because Container has associated type requirements:
func displayValues(container: Container) { ... }
class MyClass { let container: Container }
// > error: protocol 'Container' can only be used as a generic constraint
// > because it has Self or associated type requirements

// These are allowed:
func displayValues<T: Container>(container: T) { ... }
class MyClass<T: Container> { let container: T }

Un tipo que se ajusta al protocolo puede satisfacer un requisito de tipo associatedtype implícitamente, al proporcionar un tipo dado en el que el protocolo espera que aparezca el tipo associatedtype :

struct ContainerOfOne<T>: Container {
    let count = 1          // satisfy the count requirement
    var value: T
    
    // satisfy the subscript associatedtype requirement implicitly,
    // by defining the subscript assignment/return type as T
    // therefore Swift will infer that T == Element
    subscript(index: Int) -> T {
        get {
            precondition(index == 0)
            return value
        }
        set {
            precondition(index == 0)
            value = newValue
        }
    }
}

let container = ContainerOfOne(value: "Hello")

(Tenga en cuenta que para agregar claridad a este ejemplo, el tipo de marcador de posición genérico se denomina T ; un nombre más adecuado sería Element , lo que ocultaría el associatedtype Element del protocolo. El compilador todavía inferirá que el Element marcador de posición genérico se utiliza para satisfacer el tipo associatedtype Element Requisito del associatedtype Element .)

Un tipo associatedtype también puede satisfacerse explícitamente mediante el uso de typealias :

struct ContainerOfOne<T>: Container {

    typealias Element = T
    subscript(index: Int) -> Element { ... }

    // ...
}

Lo mismo ocurre con las extensiones:

// Expose an 8-bit integer as a collection of boolean values (one for each bit).
extension UInt8: Container {

    // as noted above, this typealias can be inferred
    typealias Element = Bool

    var count: Int { return 8 }
    subscript(index: Int) -> Bool {
        get {
            precondition(0 <= index && index < 8)
            return self & 1 << UInt8(index) != 0
        }
        set {
            precondition(0 <= index && index < 8)
            if newValue {
                self |= 1 << UInt8(index)
            } else {
                self &= ~(1 << UInt8(index))
            }
        }
    }
}

Si el tipo conforme ya cumple con el requisito, no se necesita implementación:

extension Array: Container {}  // Array satisfies all requirements, including Element

Patrón de delegado

Un delegado es un patrón de diseño común utilizado en los marcos de Cocoa y CocoaTouch, donde una clase delega la responsabilidad de implementar alguna funcionalidad a otra. Esto sigue un principio de separación de preocupaciones, donde la clase marco implementa una funcionalidad genérica mientras que una instancia delegada separada implementa el caso de uso específico.

Otra forma de ver el patrón de delegado es en términos de comunicación de objetos. Objects menudo necesitan conversar entre ellos y para hacerlo, un objeto debe ajustarse a un protocol para convertirse en un delegado de otro Objeto. Una vez que se ha realizado esta configuración, el otro objeto responde a sus delegados cuando suceden cosas interesantes.

Por ejemplo, una vista en la interfaz de usuario para mostrar una lista de datos debe ser responsable solo de la lógica de cómo se muestran los datos, no de decidir qué datos deben mostrarse.

Vayamos a un ejemplo más concreto. Si tienes dos clases, un padre y un niño:

class Parent { }
class Child { }

Y desea notificar a los padres de un cambio del niño.

En Swift, los delegados se implementan utilizando una declaración de protocol por lo que declararemos un protocol que implementará el delegate . Aquí el delegado es el objeto parent .

protocol ChildDelegate: class {
    func childDidSomething()
}

El niño debe declarar una propiedad para almacenar la referencia al delegado:

class Child {
    weak var delegate: ChildDelegate?
}

Observe la variable delegate es un opcional y el protocolo ChildDelegate está marcado sólo a ser implementado por tipo de clase (sin esto el delegate variable no puede ser declarada como una weak referencia evitando cualquier ciclo conservan. Esto significa que si el delegate la variable ya no es referenciado en cualquier otro lugar, será lanzado). Esto es para que la clase padre solo registre al delegado cuando sea necesario y esté disponible.

También para marcar a nuestro delegado como weak , debemos restringir nuestro protocolo ChildDelegate a tipos de referencia agregando palabras clave de class en la declaración de protocolo.

En este ejemplo, cuando el niño hace algo y necesita notificar a su padre, el niño llamará:

delegate?.childDidSomething()

Si el delegado ha sido definido, se le notificará al delegado que el niño ha hecho algo.

La clase principal deberá extender el protocolo ChildDelegate para poder responder a sus acciones. Esto se puede hacer directamente en la clase padre:

class Parent: ChildDelegate {
    ...

    func childDidSomething() {
        print("Yay!")
    }
}

O usando una extensión:

extension Parent: ChildDelegate {
    func childDidSomething() {
        print("Yay!")
    }
}

El padre también debe decirle al niño que es el delegado del niño:

// In the parent
let child = Child()
child.delegate = self

Por defecto, un protocol Swift no permite implementar una función opcional. Solo se pueden especificar si su protocolo está marcado con el atributo @objc y el modificador optional .

Por ejemplo, UITableView implementa el comportamiento genérico de una vista de tabla en iOS, pero el usuario debe implementar dos clases delegadas llamadas UITableViewDelegate y UITableViewDataSource que implementan el aspecto y el comportamiento de las células específicas.

@objc public protocol UITableViewDelegate : NSObjectProtocol, UIScrollViewDelegate {
        
        // Display customization
        optional public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
        optional public func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int)
        optional public func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int)
        optional public func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
        ...
}

Puede implementar este protocolo cambiando su definición de clase, por ejemplo:

class MyViewController : UIViewController, UITableViewDelegate

Se debe implementar cualquier método que no esté marcado como optional en la definición de protocolo ( UITableViewDelegate en este caso).

Ampliación del protocolo para una clase específica conforme

Puede escribir la implementación del protocolo predeterminado para una clase específica.

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol where Self: UIViewController {
    func doSomething() {
        print("UIViewController default protocol implementation")
    }
}

class MyViewController: UIViewController, MyProtocol { }

let vc = MyViewController()
vc.doSomething() // Prints "UIViewController default protocol implementation"

Usando el protocolo RawRepresentable (Extensible Enum)

// RawRepresentable has an associatedType RawValue.
// For this struct, we will make the compiler infer the type
// by implementing the rawValue variable with a type of String
//
// Compiler infers RawValue = String without needing typealias
//
struct NotificationName: RawRepresentable {
    let rawValue: String

    static let dataFinished = NotificationNames(rawValue: "DataFinishedNotification")
}

Esta estructura puede extenderse a otro lugar para agregar casos

extension NotificationName {
    static let documentationLaunched = NotificationNames(rawValue: "DocumentationLaunchedNotification")
}

Y una interfaz puede diseñar alrededor de cualquier tipo de RawRepresentable o específicamente su estructura de enumeración

func post(notification notification: NotificationName) -> Void {
    // use notification.rawValue
}

En el sitio de la llamada, puede usar la sintaxis de puntos para el NotificationName

post(notification: .dataFinished)

Usando la función genérica de RawRepresentable

// RawRepresentable has an associate type, so the 
// method that wants to accept any type conforming to
// RawRepresentable needs to be generic
func observe<T: RawRepresentable>(object: T) -> Void {
    // object.rawValue
}

Protocolos de clase solamente

Un protocolo puede especificar que solo una clase puede implementarlo mediante el uso de la palabra clave de class en su lista de herencia. Esta palabra clave debe aparecer antes que cualquier otro protocolo heredado en esta lista.

protocol ClassOnlyProtocol: class, SomeOtherProtocol {
    // Protocol requirements 
}

Si un tipo que no es de clase intenta implementar ClassOnlyProtocol , se ClassOnlyProtocol un error del compilador.

struct MyStruct: ClassOnlyProtocol { 
    // error: Non-class type 'MyStruct' cannot conform to class protocol 'ClassOnlyProtocol'  
}

Otros protocolos pueden heredar de ClassOnlyProtocol , pero tendrán el mismo requisito de clase solamente.

protocol MyProtocol: ClassOnlyProtocol {
    // ClassOnlyProtocol Requirements
    // MyProtocol Requirements
}

class MySecondClass: MyProtocol {
    // ClassOnlyProtocol Requirements
    // MyProtocol Requirements
}

Semántica de referencia de protocolos de clase solamente.

El uso de un protocolo solo de clase permite la semántica de referencia cuando el tipo de conformidad es desconocido.

protocol Foo : class {
    var bar : String { get set }
}

func takesAFoo(foo:Foo) {

    // this assignment requires reference semantics,
    // as foo is a let constant in this scope.
    foo.bar = "new value"
}

En este ejemplo, como Foo es un protocolo de solo clase, la asignación a la bar es válida ya que el compilador sabe que foo es un tipo de clase, y por lo tanto tiene una semántica de referencia.

Si Foo no fuera un protocolo solo de clase, se produciría un error de compilación, ya que el tipo conforme podría ser un tipo de valor , que requeriría una anotación de var para poder ser modificable.

protocol Foo {
    var bar : String { get set }
}

func takesAFoo(foo:Foo) {
    foo.bar = "new value" // error: Cannot assign to property: 'foo' is a 'let' constant
}

func takesAFoo(foo:Foo) {
    var foo = foo // mutable copy of foo
    foo.bar = "new value" // no error – satisfies both reference and value semantics
}

Variables débiles del tipo de protocolo.

Al aplicar el modificador weak a una variable de tipo de protocolo, ese tipo de protocolo debe ser solo de clase, ya que weak solo se puede aplicar a tipos de referencia.

weak var weakReference : ClassOnlyProtocol?

Implementando el protocolo Hashable

Los tipos utilizados en Sets y Dictionaries(key) deben cumplir con el protocolo Hashable que se hereda del protocolo Equatable .

Hashable debe implementar el tipo personalizado conforme al protocolo Hashable

  • Una propiedad calculada hashValue
  • Defina uno de los operadores de igualdad, es decir == o != .

El siguiente ejemplo implementa el protocolo Hashable para una struct personalizada:

struct Cell {
    var row: Int
    var col: Int
    
    init(_ row: Int, _ col: Int) {
        self.row = row
        self.col = col
    }
}

extension Cell: Hashable {
    
    // Satisfy Hashable requirement
    var hashValue: Int {
        get {
            return row.hashValue^col.hashValue
        }
    }

    // Satisfy Equatable requirement
    static func ==(lhs: Cell, rhs: Cell) -> Bool {
        return lhs.col == rhs.col && lhs.row == rhs.row
    }
    
}

// Now we can make Cell as key of dictonary
var dict = [Cell : String]()

dict[Cell(0, 0)] = "0, 0"
dict[Cell(1, 0)] = "1, 0"
dict[Cell(0, 1)] = "0, 1"

// Also we can create Set of Cells
var set = Set<Cell>()

set.insert(Cell(0, 0))
set.insert(Cell(1, 0))

Nota : No es necesario que los diferentes valores en el tipo personalizado tengan diferentes valores hash, las colisiones son aceptables. Si los valores de hash son iguales, el operador de igualdad se utilizará para determinar la igualdad real.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow