Come definire metodi opzionali nel protocollo Swift?


359

È possibile in Swift? In caso contrario, esiste una soluzione alternativa per farlo?


1
Alla fine mi sono motivato ad aggiornare la mia risposta, puoi riconsiderarla accettandola.
Akashivskyy

@akashivskyy Accetto la tua risposta perché mostra meglio tutte le opzioni disponibili e i loro pro e contro.
Selvin,

Risposte:


506

1. Utilizzo di implementazioni predefinite (preferito).

protocol MyProtocol {
    func doSomething()
}

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

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

vantaggi

  • Nessun runtime Objective-C è coinvolto (beh, non almeno esplicitamente). Ciò significa che puoi conformare strutture, enumerazioni e non NSObjectclassi ad esso. Inoltre, questo significa che puoi sfruttare il potente sistema di generici.

  • Puoi sempre essere sicuro che tutti i requisiti siano soddisfatti quando incontri tipi conformi a tale protocollo. È sempre un'implementazione concreta o di default. Ecco come "interfacce" o "contratti" si comportano in altre lingue.

svantaggi

  • Per i non Voidrequisiti, è necessario disporre di un valore predefinito ragionevole , che non è sempre possibile. Tuttavia, quando si riscontra questo problema, significa che o tale requisito non dovrebbe avere un'implementazione predefinita o che l'utente ha commesso un errore durante la progettazione dell'API.

  • Non è possibile distinguere tra un'implementazione predefinita e nessuna implementazione , almeno senza affrontare quel problema con valori di ritorno speciali. Considera il seguente esempio:

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

    Se si fornisce un'implementazione predefinita che restituisce true, va bene a prima vista. Ora, considera il seguente pseudo codice:

    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 */
            }
        }
    }

    Non c'è modo di implementare tale ottimizzazione: non puoi sapere se il tuo delegato implementa un metodo o meno.

    Sebbene esistano diversi modi per ovviare a questo problema (utilizzando chiusure opzionali, oggetti delegati diversi per diverse operazioni per citarne alcuni), questo esempio presenta chiaramente il problema.


2. Utilizzo @objc optional.

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

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

vantaggi

  • Non è necessaria alcuna implementazione predefinita. Dichiarate semplicemente un metodo opzionale o una variabile e siete pronti per iniziare.

svantaggi

  • Limita fortemente le capacità del protocollo richiedendo che tutti i tipi conformi siano compatibili con Objective-C. Ciò significa che solo le classi che ereditano NSObjectpossono conformarsi a tale protocollo. Nessuna struttura, nessun enumerazione, nessun tipo associato.

  • È sempre necessario verificare se un metodo opzionale è implementato chiamando facoltativamente o controllando se il tipo conforme lo implementa. Questo potrebbe introdurre molta caldaia se chiamate spesso metodi opzionali.


17
Guarda come migliorare i metodi opzionali in modo rapido usando un'estensione (sotto)
Daniel Kanaan,

1
E come si fa a testare il supporto di un metodo di protocollo opzionale in un'istanza? respondsToSelector?
devios1

3
Basta non farlo! Swift non supporta il metodo opzionale nei protocolli per un motivo.
fpg1503,

2
Questo metodo non supporta gli optional nei parametri. Cioè non puoi farlooptional func doSomething(param: Int?)
SoftDesigner

4
Sicuramente questa risposta è sostanzialmente sbagliata al giorno d'oggi, anni dopo. Oggi in Swift aggiungi semplicemente un'estensione per l'implementazione predefinita, è un aspetto di base di Swift. (Come mostrano tutte le risposte moderne di seguito). Al giorno d'oggi sarebbe sbagliato aggiungere la bandiera objc.
Fattie,

394

In Swift 2 e versioni successive è possibile aggiungere implementazioni predefinite di un protocollo. Questo crea un nuovo modo di metodi opzionali nei protocolli.

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

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

Non è un modo davvero carino per creare metodi di protocollo opzionali, ma ti dà la possibilità di usare le strutture nei callback di protocollo.

Ho scritto un piccolo riassunto qui: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/


2
Questo è probabilmente il modo più pulito per farlo in Swift. Peccato che non funzioni prima di Swift 2.0.
Entalpi,

13
@MattQuiros Sto scoprendo che in effetti devi dichiarare la funzione nella definizione del protocollo, altrimenti la funzione di estensione no-op non verrà sovrascritta nelle tue classi conformi al protocollo.
Ian Pearce,

3
@IanPearce è corretto e questo sembra essere di progettazione. Nel discorso "Protocol Oriented Programming" (408) al WWDC, si parla di metodi nel protocollo principale che sono "punti di personalizzazione" offerti a tipi conformi. Un punto richiesto di personalizzazione non riceve una definizione in un'estensione; un punto facoltativo lo fa. I metodi sul protocollo che generalmente non devono essere personalizzati sono interamente dichiarati / definiti nell'estensione per non consentire la personalizzazione di tipi conformi, a meno che non si esegua il cast specifico di DynamicType per mostrare che si desidera l'implementazione personalizzata del conformatore.
Matthias,

4
@FranklinYu Puoi farlo ma poi stai inquinando il tuo design API con "tiri" dove in realtà non è necessario. Mi piace di più l'idea di "microprotocolli". Ad esempio, ogni metodo conferma un protocollo e quindi è possibile verificare: se l'oggetto è protocollo
Darko,

3
@Antoine Penso che dovresti rendere pubblica la funzione nell'estensione, poiché le funzioni nei protocolli sono pubbliche per definizione. La tua soluzione non funzionerà quando il protocollo verrà utilizzato al di fuori del suo modulo.
Vadim Eisenberg,

39

Poiché ci sono alcune risposte su come utilizzare il modificatore opzionale e l'attributo @objc per definire il protocollo dei requisiti facoltativi, fornirò un esempio su come utilizzare le estensioni di protocollo per definire il protocollo opzionale.

Il codice seguente è 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*

Si noti che i metodi di estensione del protocollo non possono essere invocati dal codice Objective-C e, peggio ancora, il team Swift non lo risolverà. https://bugs.swift.org/browse/SR-492


1
Buon lavoro! Questa dovrebbe essere la risposta corretta poiché risolve il problema senza coinvolgere il runtime di Objective-C.
Lukas,

davvero bello!
tania_S,

dalla rapida documentazione - "I requisiti del protocollo con implementazioni predefinite fornite dalle estensioni sono distinti dai requisiti del protocollo opzionale. Sebbene i tipi conformi non debbano fornire la propria implementazione di nessuno dei due, i requisiti con implementazioni predefinite possono essere chiamati senza concatenamento opzionale."
Mec Os,

34

Le altre risposte qui che riguardano la marcatura del protocollo come "@objc" non funzionano quando si utilizzano tipi specifici di 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"

Al fine di dichiarare protocolli opzionali che funzionano bene con swift, dichiarare le funzioni come variabili invece di funzioni.

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

E quindi implementare il protocollo come segue

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

È quindi possibile utilizzare "?" per verificare se la funzione è stata implementata o meno

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

var anEntity: Health = returnEntity()

var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
//"isHealthy" is true

3
Con questa soluzione non è possibile accedere a sé da nessuna delle funzioni del protocollo. Ciò può causare problemi in alcuni casi!
George Green,

1
@GeorgeGreen Puoi accedere a te stesso. Contrassegna la variabile funzione come lazy e usa un elenco di acquisizione all'interno della chiusura .
Zag

Solo classi, protocolli, metodi e proprietà possono usare @objc. Nel caso in cui si stia utilizzando un parametro Enum nella definizione del metodo del protocollo @ objc, si è condannati.
Khunshan,

1
@khunshan, questo metodo non richiede la marcatura di @ objc, a cosa ti riferisci?
Zag

è un'informazione sull'argomento che enum non può essere usata tra swift e objc e che per le altre istruzioni può essere colmata con la parola chiave @objc.
Khunshan,

34

Ecco un esempio concreto con il modello di delega.

Configura il tuo protocollo:

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

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

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

Impostare il delegato su una classe e implementare il protocollo. Verifica che non è necessario implementare il metodo opzionale.

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

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

    func requiredMethod()
    {
    }
}

Una cosa importante è che il metodo opzionale è opzionale e necessita di un "?" quando si chiama. Menziona il secondo punto interrogativo.

delegate?.optionalMethod?()

2
Questa dovrebbe essere la risposta corretta. Semplice, pulito ed esplicativo.
Echelon,

sono d'accordo, questa risposta è breve, dolce e diretta al punto. Grazie!
LuAndre,

31

In Swift 3.0

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

Ti farà risparmiare tempo.


6
Qualche fonte del perché @objcè necessaria per tutti i membri ora?
DevAndArtist

@Gautam Sareriya: Cosa pensi sia meglio questo approccio o la creazione di metodi di estensione vuoti?
eonista,

Ho provato con i tuoi metodi con requiredflag, ma con errori: requiredpuò essere utilizzato solo su dichiarazioni "init".
Ben,

27
  • Devi aggiungere una optionalparola chiave prima di ogni metodo.
  • Si noti, tuttavia, che per farlo funzionare, il protocollo deve essere contrassegnato con l'attributo @objc.
  • Ciò implica inoltre che questo protocollo sarebbe applicabile alle classi ma non alle strutture.

Correzione minore (troppo piccola per essere modificata!) Ma dovrebbe essere 'opzionale' non '@opzionale'
Ali Beadle

5
Ciò richiede anche di contrassegnare la classe che implementa il protocollo come @objc, non solo il protocollo.
Johnathon Sullinger,

13

Un puro approccio Swift con eredità di protocollo:

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

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

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

11

Per illustrare i meccanismi della risposta di 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"

8

Penso che prima di chiederti come implementare un metodo di protocollo opzionale, dovresti chiederti perché dovresti implementarne uno.

Se pensiamo di protocolli veloci come interfaccia nella classica programmazione orientata agli oggetti, metodi facoltativi non hanno molto senso, e forse una soluzione migliore sarebbe quella di creare implementazione di default, o separare il protocollo in una serie di protocolli (magari con alcune relazioni di ereditarietà tra loro) per rappresentare la possibile combinazione di metodi nel protocollo.

Per ulteriori informazioni, consultare https://useyourloaf.com/blog/swift-optional-protocol-methods/ , che offre un'eccellente panoramica sull'argomento.


Questo. È aderenza al principio "I" nei principi di sviluppo del software SOLID .
Eric

7

Leggermente fuori tema rispetto alla domanda originale, ma sviluppa l'idea di Antoine e ho pensato che potesse aiutare qualcuno.

È inoltre possibile rendere facoltative le proprietà calcolate per le strutture con estensioni di protocollo.

È possibile rendere facoltativa una proprietà sul protocollo

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

Implementare la proprietà calcolata fittizia nell'estensione del protocollo

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

E ora puoi usare le strutture che hanno o non hanno implementata la proprietà opzionale

struct ConformsWithoutOptional {
    let required: String
}

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

Ho anche scritto come fare proprietà opzionali nei protocolli Swift sul mio blog , che terrò aggiornato nel caso in cui le cose cambino attraverso le versioni di Swift 2.


5

Come creare metodi delegati facoltativi e richiesti.

@objc protocol InterViewDelegate:class {

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

}

3

Ecco un esempio molto semplice SOLO per le classi veloci e non per strutture o enumerazioni. Si noti che il metodo del protocollo è facoltativo, ha due livelli di concatenamento opzionale in gioco. Anche la classe che adotta il protocollo necessita dell'attributo @objc nella sua dichiarazione.

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

 }

Puoi descrivere un po 'di più la parte " due livelli di optional at play ", vale a dire questo delegate?.indexDidChange?(index!):?
Unheilig

2
Se avessimo scritto il protocollo per avere un metodo non opzionale come questo: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } lo chiameresti senza il punto interrogativo: delegate?.indexDidChange(index!) quando imposti un requisito opzionale per un metodo in un protocollo, il Tipo che sarà conforme a esso potrebbe NON implementare quel metodo , quindi ?viene usato per verificare l'implementazione e se non ce n'è nessuno il programma non si arresterà in modo anomalo. @Unheilig
Blessing Lopes

weak var delegate : CollectionOfDataDelegate?(assicurare un riferimento debole?)
Alfie Hanssen,

@BlessingLopes Puoi aggiungere la tua spiegazione delegate?sull'utilizzo alla tua risposta? Tali informazioni dovrebbero davvero appartenere agli altri in futuro. Voglio votare questo, ma le informazioni dovrebbero davvero essere nella risposta.
Johnathon Sullinger,

3

se si desidera farlo in modo rapido, il modo migliore è fornire un'implementazione predefinita in particolare se si restituisce un tipo Swift come ad esempio struct con tipi Swift

esempio :

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

quindi è possibile implementare il protocollo senza definire ogni funzione


2

Esistono due modi per creare un metodo opzionale nel protocollo rapido.

1 - La prima opzione è quella di contrassegnare il protocollo usando l'attributo @objc. Sebbene ciò significhi che può essere adottato solo dalle classi, significa che contrassegni i singoli metodi come facoltativi in ​​questo modo:

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

2 - Un modo più veloce: questa opzione è migliore. Scrivi implementazioni predefinite dei metodi opzionali che non fanno nulla, come questo.

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

extension MyProtocol {

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

Swift ha una funzione chiamata extension che ci consente di fornire un'implementazione predefinita per quei metodi che vogliamo essere facoltativi.


0

Un'opzione è di memorizzarli come variabili di funzione opzionali:

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

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)

-1

Definire la funzione nel protocollo e creare l'estensione per quel protocollo, quindi creare un'implementazione vuota per la funzione che si desidera utilizzare come facoltativa.


-2

Per definire Optional Protocolrapidamente è necessario utilizzare la @objcparola chiave prima della Protocoldichiarazione e attribute/ / methoddichiarazione all'interno di quel protocollo. Di seguito è riportato un esempio di Proprietà facoltativa di un protocollo.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}

2
Mentre questo può rispondere alla domanda, è meglio aggiungere una descrizione di come questa risposta possa aiutare a risolvere il problema. Si prega di leggere Come posso scrivere una buona risposta per saperne di più.
Roshana Pitigala,

-23

Metti @optionaldi fronte metodi o proprietà.


Errore del compilatore: l'attributo 'opzionale' può essere applicato solo ai membri di un protocollo @objc.
Selvin,

3
@optionalnon è nemmeno la parola chiave corretta. È optional, e devi dichiarare la classe e il protocollo con l' @objcattributo.
Johnathon Sullinger,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.