¿Cómo definir métodos opcionales en el protocolo Swift?

359

¿Es posible en Swift? Si no es así, ¿hay alguna solución para hacerlo?

Selvin
fuente
1
Finalmente me motivé a actualizar mi respuesta, puedes reconsiderar aceptarla.
akashivskyy
@akashivskyy Acepto su respuesta porque muestra mejor todas las opciones disponibles y sus ventajas y desventajas.
Selvin

Respuestas:

506

1. Uso de implementaciones predeterminadas (preferidas).

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol {
    func doSomething() {
        /* return a default value or just leave empty */
    }
}

struct MyStruct: MyProtocol {
    /* no compile error */
}

Ventajas

  • No hay tiempo de ejecución de Objective-C involucrado (bueno, al menos no explícitamente). Esto significa que puede conformar estructuras, enumeraciones y no NSObjectclases. Además, esto significa que puede aprovechar el potente sistema de genéricos.

  • Siempre puede estar seguro de que se cumplen todos los requisitos al encontrar tipos que se ajustan a dicho protocolo. Siempre es una implementación concreta o una predeterminada. Así es como se comportan las "interfaces" o los "contratos" en otros idiomas.

Desventajas

  • Para los no Voidrequisitos, debe tener un valor predeterminado razonable , que no siempre es posible. Sin embargo, cuando encuentre este problema, significa que tal requisito realmente no debería tener una implementación predeterminada o que cometió un error durante el diseño de la API.

  • No puede distinguir entre una implementación predeterminada y ninguna implementación , al menos sin abordar ese problema con valores de retorno especiales. Considere el siguiente ejemplo:

    protocol SomeParserDelegate {
        func validate(value: Any) -> Bool
    }

    Si proporciona una implementación predeterminada que solo regresa true, está bien a primera vista. Ahora, considere el siguiente pseudocódigo:

    final class SomeParser {
        func parse(data: Data) -> [Any] {
            if /* delegate.validate(value:) is not implemented */ {
                /* parse very fast without validating */
            } else {
                /* parse and validate every value */
            }
        }
    }

    No hay forma de implementar tal optimización: no puede saber si su delegado implementa un método o no.

    Aunque hay varias maneras diferentes de superar este problema (usando cierres opcionales, diferentes objetos delegados para diferentes operaciones, por nombrar algunos), ese ejemplo presenta el problema claramente.


2. Utilizando @objc optional.

@objc protocol MyProtocol {
    @objc optional func doSomething()
}

class MyClass: NSObject, MyProtocol {
    /* no compile error */
}

Ventajas

  • No se necesita una implementación predeterminada. Simplemente declaras un método opcional o una variable y estás listo para comenzar.

Desventajas

  • Limita severamente las capacidades de su protocolo al requerir que todos los tipos conformes sean compatibles con Objective-C. Esto significa que solo las clases que heredan de NSObjectpueden ajustarse a dicho protocolo. Sin estructuras, sin enumeraciones, sin tipos asociados.

  • Siempre debe verificar si se implementa un método opcional llamando o verificando opcionalmente si el tipo conforme lo implementa. Esto podría introducir una gran cantidad de repeticiones si llama a métodos opcionales con frecuencia.

akashivskyy
fuente
17
Mire la forma mejorada de métodos opcionales en Swift usando una extensión (abajo)
Daniel Kanaan
1
¿Y cómo se prueba la compatibilidad de un método de protocolo opcional en una instancia? respondsToSelector?
devios1
3
¡Simplemente no hagas esto! Swift no admite el método opcional en protocolos por una razón.
fpg1503
2
Este método no admite opciones en parámetros. Es decir, no puedes hacer esooptional func doSomething(param: Int?)
SoftDesigner
44
Seguramente esta respuesta es esencialmente incorrecta hoy en día, años después. Hoy en Swift simplemente agrega una extensión para la implementación predeterminada, es un aspecto básico de Swift. (Como muestran todas las respuestas modernas a continuación.) Sería un error agregar la bandera objc, hoy en día.
Fattie
394

En Swift 2 y en adelante, es posible agregar implementaciones predeterminadas de un protocolo. Esto crea una nueva forma de métodos opcionales en los protocolos.

protocol MyProtocol {
    func doSomethingNonOptionalMethod()
    func doSomethingOptionalMethod()
}

extension MyProtocol {
    func doSomethingOptionalMethod(){ 
        // leaving this empty 
    }
}

No es una forma realmente agradable de crear métodos de protocolo opcionales, pero le brinda la posibilidad de usar estructuras en devoluciones de llamada de protocolo.

Escribí un pequeño resumen aquí: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/

Antoine
fuente
2
Esta es probablemente la forma más limpia de hacerlo en Swift. Lástima que no funcione antes de Swift 2.0.
Entalpi
13
@MattQuiros Estoy descubriendo que, de hecho, debe declarar la función en la definición del protocolo, de lo contrario, la función de extensión no operativa no se anula en sus clases que se ajustan al protocolo.
Ian Pearce
3
@IanPearce es correcto, y esto parece ser por diseño. En la charla "Programación orientada al protocolo" (408) en WWDC, hablan sobre métodos en el protocolo principal que son "puntos de personalización" ofrecidos a los tipos conformes. Un punto de personalización requerido no recibe una definición en una extensión; un punto opcional hace. Los métodos en el protocolo que generalmente no deben personalizarse se declaran / definen por completo en la extensión para no permitir que los tipos conformes se personalicen a menos que específicamente se reduzca a su tipo dinámico para mostrar que desea la implementación personalizada del conformador.
Matthias
44
@FranklinYu Puede hacer esto, pero luego está contaminando su diseño de API con 'tiros' donde de hecho no es necesario. Me gusta más la idea de "micro protocolos". Por ejemplo, cada método confirma un protocolo y luego puede verificar si el objeto es protocolo
Darko
3
@Antoine Creo que deberías hacer pública la función en la extensión, ya que las funciones en los protocolos son públicas por definición. Su solución no funcionará cuando el protocolo se use fuera de su módulo.
Vadim Eisenberg
39

Dado que hay algunas respuestas sobre cómo usar el modificador opcional y el atributo @objc para definir el protocolo de requisitos opcionales, daré una muestra sobre cómo usar las extensiones de protocolo para definir el protocolo opcional.

El siguiente código es Swift 3. *.

/// Protocol has empty default implementation of the following methods making them optional to implement:
/// `cancel()`
protocol Cancelable {

    /// default implementation is empty.
    func cancel()
}

extension Cancelable {

    func cancel() {}
}

class Plane: Cancelable {
  //Since cancel() have default implementation, that is optional to class Plane
}

let plane = Plane()
plane.cancel()
// Print out *United Airlines can't cancelable*

Tenga en cuenta que los métodos de extensión de protocolo no pueden ser invocados por el código Objective-C, y lo peor es que el equipo Swift no lo solucionará. https://bugs.swift.org/browse/SR-492

Eddie.Dou
fuente
1
¡Buen trabajo! Esta debería ser la respuesta correcta ya que resuelve el problema sin involucrar el tiempo de ejecución de Objective-C.
Lukas
realmente agradable!
tania_S
de la documentación rápida: "Los requisitos de protocolo con implementaciones predeterminadas proporcionadas por extensiones son distintos de los requisitos de protocolo opcionales. Aunque los tipos conformes no tienen que proporcionar su propia implementación, los requisitos con implementaciones predeterminadas se pueden invocar sin encadenamiento opcional".
Mec Os
34

Las otras respuestas aquí que implican marcar el protocolo como "@objc" no funcionan cuando se usan tipos específicos de swift.

struct Info {
    var height: Int
    var weight: Int
} 

@objc protocol Health {
    func isInfoHealthy(info: Info) -> Bool
} 
//Error "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C"

Para declarar protocolos opcionales que funcionen bien con swift, declare las funciones como variables en lugar de funciones.

protocol Health {
    var isInfoHealthy: (Info) -> (Bool)? { get set }
}

Y luego implemente el protocolo de la siguiente manera

class Human: Health {
    var isInfoHealthy: (Info) -> (Bool)? = { info in
        if info.weight < 200 && info.height > 72 {
            return true
        }
        return false
    }
    //Or leave out the implementation and declare it as:  
    //var isInfoHealthy: (Info) -> (Bool)?
}

Entonces puedes usar "?" para verificar si la función se ha implementado o no

func returnEntity() -> Health {
    return Human()
}

var anEntity: Health = returnEntity()

var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
//"isHealthy" is true
Zag
fuente
3
Con esta solución no puede acceder a sí mismo desde ninguna de las funciones del protocolo. ¡Esto puede causar problemas en algunos casos!
George Green
1
@GeorgeGreen Puedes acceder a ti mismo. Marque la variable de función como perezosa y use una lista de captura dentro del cierre .
Zag
Solo las clases, protocolos, métodos y propiedades pueden usar @objc. En caso de que esté utilizando un parámetro Enum en la definición del método de protocolo @ objc, está condenado.
Khunshan
1
@khunshan, este método no requiere que se marque nada @ objc, ¿a qué se refiere?
Zag
es una información sobre el tema de que las enumeraciones no se pueden usar entre swift y objc y que las otras declaraciones se pueden unir con la palabra clave @objc.
Khunshan
34

Aquí hay un ejemplo concreto con el patrón de delegación.

Configure su protocolo:

@objc protocol MyProtocol:class
{
    func requiredMethod()
    optional func optionalMethod()
}

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

    func callDelegate()
    {
        delegate?.requiredMethod()
        delegate?.optionalMethod?()
    }
}

Establezca el delegado en una clase e implemente el Protocolo. Ver que el método opcional no necesita ser implementado.

class AnotherClass: NSObject, MyProtocol
{
    init()
    {
        super.init()

        let myInstance = MyClass()
        myInstance.delegate = self
    }

    func requiredMethod()
    {
    }
}

Una cosa importante es que el método opcional es opcional y necesita un "?" al llamar Mencione el segundo signo de interrogación.

delegate?.optionalMethod?()
Rafael
fuente
2
Esta debería ser la respuesta correcta. Simple, limpio y explicativo.
Echelon
Estoy de acuerdo, esta respuesta es corta, dulce y directa al grano. ¡Gracias!
LuAndre
31

En Swift 3.0

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

Te ahorrará tiempo.

Gautam Sareriya
fuente
66
¿Alguna fuente de por qué @objcse necesita en todos los miembros ahora?
DevAndArtist
@Gautam Sareriya: ¿Cuál crees que es mejor este enfoque o la creación de métodos de extensión vacíos?
eonista
Intenté con sus métodos con requiredflag, pero con errores: requiredsolo se puede usar en declaraciones 'init'.
Ben
27
  • Debe agregar una optionalpalabra clave antes de cada método.
  • Sin embargo, tenga en cuenta que para que esto funcione, su protocolo debe estar marcado con el atributo @objc.
  • Esto implica además que este protocolo sería aplicable a las clases pero no a las estructuras.
Mehul Parmar
fuente
Corrección menor (¡demasiado pequeña para editar!) Pero debería ser 'opcional', no '@opcional'
Ali Beadle
55
Esto también requiere que marque su clase implementando el protocolo como @objc, no solo el protocolo.
Johnathon Sullinger
13

Un enfoque puro de Swift con herencia de protocolo:

//Required methods
protocol MyProtocol {
    func foo()
}

//Optional methods
protocol MyExtendedProtocol: MyProtocol {
    func bar()
}

class MyClass {
    var delegate: MyProtocol
    func myMethod() {
        (delegate as? MyExtendedProtocol).bar()
    }
}
guenis
fuente
11

Para ilustrar la mecánica de la respuesta de Antoine:

protocol SomeProtocol {
    func aMethod()
}

extension SomeProtocol {
    func aMethod() {
        print("extensionImplementation")
    }
}

class protocolImplementingObject: SomeProtocol {

}

class protocolImplementingMethodOverridingObject: SomeProtocol {
    func aMethod() {
        print("classImplementation")
    }
}

let noOverride = protocolImplementingObject()
let override = protocolImplementingMethodOverridingObject()

noOverride.aMethod() //prints "extensionImplementation"
override.aMethod() //prints "classImplementation"
eLillie
fuente
8

Creo que antes de preguntar cómo puede implementar un método de protocolo opcional, debería preguntarse por qué debería implementar uno.

Si pensamos en los protocolos rápidos como una interfaz en la programación orientada a objetos clásica, los métodos opcionales no tienen mucho sentido, y tal vez una mejor solución sería crear una implementación predeterminada o separar el protocolo en un conjunto de protocolos (tal vez con algunas relaciones de herencia entre ellos) para representar la posible combinación de métodos en el protocolo.

Para más información, consulte https://useyourloaf.com/blog/swift-optional-protocol-methods/ , que ofrece una excelente descripción general sobre este asunto.

emem
fuente
Esta. Es la adhesión al principio "I" en los principios de desarrollo de software SOLID .
Eric
7

Ligeramente fuera del tema de la pregunta original, pero se basa en la idea de Antoine y pensé que podría ayudar a alguien.

También puede hacer que las propiedades calculadas sean opcionales para estructuras con extensiones de protocolo.

Puede hacer que una propiedad en el protocolo sea opcional

protocol SomeProtocol {
    var required: String { get }
    var optional: String? { get }
}

Implemente la propiedad calculada ficticia en la extensión de protocolo

extension SomeProtocol {
    var optional: String? { return nil }
}

Y ahora puede usar estructuras que tienen o no implementada la propiedad opcional

struct ConformsWithoutOptional {
    let required: String
}

struct ConformsWithOptional {
    let required: String
    let optional: String?
}

También he escrito cómo hacer propiedades opcionales en los protocolos Swift en mi blog , que mantendré actualizado en caso de que las cosas cambien a través de las versiones de Swift 2.

matthewpalmer
fuente
5

Cómo crear métodos delegados opcionales y necesarios.

@objc protocol InterViewDelegate:class {

    @objc optional func optfunc()  //    This is optional
    func requiredfunc()//     This is required 

}
Saurabh Sharma
fuente
3

Aquí hay un ejemplo muy simple para clases rápidas SOLAMENTE, y no para estructuras o enumeraciones. Tenga en cuenta que el método de protocolo es opcional, tiene dos niveles de encadenamiento opcional en juego. Además, la clase que adopta el protocolo necesita el atributo @objc en su declaración.

@objc protocol CollectionOfDataDelegate{
   optional func indexDidChange(index: Int)
}


@objc class RootView: CollectionOfDataDelegate{

    var data = CollectionOfData()

   init(){
      data.delegate = self
      data.indexIsNow()
   }

  func indexDidChange(index: Int) {
      println("The index is currently: \(index)")
  }

}

class CollectionOfData{
    var index : Int?
    weak var delegate : CollectionOfDataDelegate?

   func indexIsNow(){
      index = 23
      delegate?.indexDidChange?(index!)
    }

 }
Bendición Lopes
fuente
¿Puedes describir un poco más la parte de " dos niveles de juego opcional ", a saber, esto delegate?.indexDidChange?(index!):?
Unheilig
2
Si hubiéramos escrito el protocolo para tener un método no opcional como este: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } entonces lo llamaría sin el signo de interrogación: delegate?.indexDidChange(index!) cuando establece un requisito opcional para un método en un protocolo, el Tipo que se ajuste a él NO podría implementar ese método , por lo que ?se utiliza para verificar la implementación y, si no hay ninguna, el programa no se bloqueará. @Unheilig
Bendición Lopes
weak var delegate : CollectionOfDataDelegate?(¿asegurar una referencia débil?)
Alfie Hanssen
@BlessingLopes ¿Puedes agregar tu explicación del delegate?uso a tu respuesta? Esa información realmente debería pertenecer a otros en el futuro. Quiero votar esto, pero esa información realmente debería estar en la respuesta.
Johnathon Sullinger
3

si desea hacerlo de manera rápida, la mejor manera es proporcionar una implementación predeterminada particularmente si devuelve un tipo Swift como, por ejemplo, struct con tipos Swift

ejemplo:

struct magicDatas {
    var damagePoints : Int?
    var manaPoints : Int?
}

protocol magicCastDelegate {
    func castFire() -> magicDatas
    func castIce() -> magicDatas
}

extension magicCastDelegate {
    func castFire() -> magicDatas {
        return magicDatas()
    }

    func castIce() -> magicDatas {
        return magicDatas()
    }
}

entonces puede implementar el protocolo sin definir cada función

p1ckl3
fuente
2

Hay dos formas de crear un método opcional en un protocolo rápido.

1 - La primera opción es marcar su protocolo usando el atributo @objc. Si bien esto significa que solo puede ser adoptado por las clases, significa que marca métodos individuales como opcionales como este:

@objc protocol MyProtocol {
    @objc optional func optionalMethod()
}

2 - Una forma más rápida: esta opción es mejor. Escriba implementaciones predeterminadas de los métodos opcionales que no hacen nada, como este.

protocol MyProtocol {
    func optionalMethod()
    func notOptionalMethod()
}

extension MyProtocol {

    func optionalMethod() {
        //this is a empty implementation to allow this method to be optional
    }
}

Swift tiene una característica llamada extensión que nos permite proporcionar una implementación predeterminada para aquellos métodos que queremos que sean opcionales.

Himanshu
fuente
0

Una opción es almacenarlos como variables de función opcionales:

struct MyAwesomeStruct {
    var myWonderfulFunction : Optional<(Int) -> Int> = nil
}

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)
FrankByte.com
fuente
-1

Defina la función en el protocolo y cree una extensión para ese protocolo, luego cree una implementación vacía para la función que desea usar como opcional.

Rishu Gupta
fuente
-2

Para definir Optional Protocolrápidamente, debe usar la @objcpalabra clave antes de la Protocoldeclaración y attribute/ methoddeclaración dentro de ese protocolo. A continuación se muestra una muestra de la propiedad opcional de un protocolo.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}
Ankur Gupta
fuente
2
Si bien esto puede responder la pregunta, es mejor agregar alguna descripción sobre cómo esta respuesta puede ayudar a resolver el problema. Lea Cómo escribo una buena respuesta para saber más.
Roshana Pitigala
-23

Poner @optionaldelante de métodos o propiedades.

shucao
fuente
Error del compilador: el atributo 'opcional' solo se puede aplicar a los miembros de un protocolo @objc.
Selvin
3
@optionalNi siquiera es la palabra clave correcta. Es optional, y debe declarar la clase y el protocolo con el @objcatributo.
Johnathon Sullinger