Wie definiere ich optionale Methoden im Swift-Protokoll?


359

Ist es in Swift möglich? Wenn nicht, gibt es eine Problemumgehung?


1
Ich habe mich schließlich motiviert, meine Antwort zu aktualisieren. Sie können die Annahme überdenken.
Akashivskyy

@akashivskyy Ich akzeptiere Ihre Antwort, weil sie alle verfügbaren Optionen und ihre Vor- und Nachteile besser zeigt.
Selvin

Antworten:


504

1. Verwenden von Standardimplementierungen (bevorzugt).

protocol MyProtocol {
    func doSomething()
}

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

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

Vorteile

  • Es ist keine Objective-C-Laufzeit beteiligt (zumindest nicht explizit). Dies bedeutet, dass Sie Strukturen, Aufzählungen und Nichtklassen daran anpassen NSObjectkönnen. Dies bedeutet auch, dass Sie das leistungsstarke Generika-System nutzen können.

  • Sie können immer sicher sein, dass alle Anforderungen erfüllt sind, wenn Sie auf Typen stoßen, die einem solchen Protokoll entsprechen. Es ist immer entweder eine konkrete Implementierung oder eine Standardimplementierung. So verhalten sich "Schnittstellen" oder "Verträge" in anderen Sprachen.

Nachteile

  • Für Nichtanforderungen Voidbenötigen Sie einen angemessenen Standardwert , der nicht immer möglich ist. Wenn Sie jedoch auf dieses Problem stoßen, bedeutet dies, dass entweder eine solche Anforderung eigentlich keine Standardimplementierung haben sollte oder dass Sie beim API-Design einen Fehler gemacht haben.

  • Sie können nicht zwischen einer Standardimplementierung und keiner Implementierung unterscheiden , zumindest ohne dieses Problem mit speziellen Rückgabewerten zu beheben. Betrachten Sie das folgende Beispiel:

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

    Wenn Sie eine Standardimplementierung bereitstellen, die gerade zurückgegeben wird true, ist dies auf den ersten Blick in Ordnung. Betrachten Sie nun den folgenden Pseudocode:

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

    Es gibt keine Möglichkeit, eine solche Optimierung zu implementieren - Sie können nicht wissen, ob Ihr Delegat eine Methode implementiert oder nicht.

    Obwohl es verschiedene Möglichkeiten gibt, dieses Problem zu lösen (mithilfe optionaler Schließungen, verschiedener Delegierungsobjekte für verschiedene Vorgänge, um nur einige zu nennen), zeigt dieses Beispiel das Problem deutlich.


2. Verwenden von @objc optional.

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

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

Vorteile

  • Es ist keine Standardimplementierung erforderlich. Sie deklarieren einfach eine optionale Methode oder eine Variable und können loslegen.

Nachteile

  • Dies schränkt die Funktionen Ihres Protokolls erheblich ein , da alle konformen Typen Objective-C-kompatibel sein müssen. Dies bedeutet, dass nur Klassen, die von erben NSObject, einem solchen Protokoll entsprechen können. Keine Strukturen, keine Aufzählungen, keine zugehörigen Typen.

  • Sie müssen immer überprüfen, ob eine optionale Methode implementiert ist, indem Sie entweder optional aufrufen oder prüfen, ob der konforme Typ sie implementiert. Dies kann zu einer Menge Boilerplate führen, wenn Sie häufig optionale Methoden aufrufen.


17
Schauen Sie sich die verbesserte Möglichkeit optionaler Methoden in Swift mit einer Erweiterung (unten) an
Daniel Kanaan

1
Und wie testet man die Unterstützung einer optionalen Protokollmethode in einer Instanz? respondsToSelector?
Devios1

3
Tu das einfach nicht! Swift unterstützt aus einem bestimmten Grund keine optionale Methode in Protokollen.
fpg1503

2
Diese Methode unterstützt keine Optionen in Parametern. optional func doSomething(param: Int?)
Dh

4
Sicherlich ist diese Antwort heutzutage, Jahre später, im Wesentlichen falsch . Heute fügen Sie in Swift einfach eine Erweiterung für die Standardimplementierung hinzu. Dies ist ein grundlegender Aspekt von Swift. (Wie alle modernen Antworten unten zeigen.) Es wäre heutzutage einfach falsch, die objc-Flagge hinzuzufügen.
Fattie

394

Ab Swift 2 können Standardimplementierungen eines Protokolls hinzugefügt werden. Dies schafft eine neue Art optionaler Methoden in Protokollen.

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

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

Es ist keine wirklich gute Möglichkeit, optionale Protokollmethoden zu erstellen, bietet Ihnen jedoch die Möglichkeit, Strukturen in Protokollrückrufen zu verwenden.

Ich habe hier eine kleine Zusammenfassung geschrieben: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/


2
Dies ist wahrscheinlich der sauberste Weg, dies in Swift zu tun. Schade, dass es vor Swift 2.0 nicht funktioniert.
Entalpi

13
@MattQuiros Ich stelle fest, dass Sie die Funktion tatsächlich in der Protokolldefinition deklarieren müssen, da sonst die No-Op-Erweiterungsfunktion in Ihren Klassen, die dem Protokoll entsprechen, nicht überschrieben wird.
Ian Pearce

3
@IanPearce ist korrekt, und dies scheint beabsichtigt zu sein. Im Vortrag "Protocol Oriented Programming" (408) auf der WWDC wird über Methoden im Hauptprotokoll gesprochen, bei denen es sich um "Anpassungspunkte" handelt, die konformen Typen angeboten werden. Ein erforderlicher Anpassungspunkt erhält keine Definition in einer Erweiterung. ein optionaler Punkt tut. Methoden für das Protokoll, die im Allgemeinen nicht angepasst werden sollten, werden in der Erweiterung vollständig deklariert / definiert, damit konforme Typen nicht angepasst werden können, es sei denn, Sie setzen sie speziell auf ihren dynamicType um, um zu zeigen, dass Sie die benutzerdefinierte Implementierung des Konformers wünschen.
Matthias

4
@FranklinYu Sie können dies tun, aber dann verschmutzen Sie Ihr API-Design mit 'Würfen', wo es tatsächlich nicht benötigt wird. Ich mag eher die Idee von "Mikroprotokollen". ZB bestätigt jede Methode ein Protokoll und dann können Sie überprüfen, ob das Objekt Protokoll ist
Darko

3
@Antoine Ich denke, Sie sollten die Funktion in der Erweiterung öffentlich machen, da die Funktionen in den Protokollen per Definition öffentlich sind. Ihre Lösung funktioniert nicht, wenn das Protokoll außerhalb des Moduls verwendet wird.
Vadim Eisenberg

39

Da es einige Antworten zur Verwendung des optionalen Modifikators und des Attributs @objc zum Definieren des optionalen Anforderungsprotokolls gibt, werde ich ein Beispiel zur Verwendung der Protokollerweiterungen zum Definieren des optionalen Protokolls geben.

Der folgende Code ist 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*

Bitte beachten Sie, dass Protokollerweiterungsmethoden nicht von Objective-C-Code aufgerufen werden können. Schlimmer ist, dass das Swift-Team dies nicht behebt. https://bugs.swift.org/browse/SR-492


1
Gut gemacht! Dies sollte die richtige Antwort sein, da das Problem dadurch gelöst wird, ohne dass die Objective-C-Laufzeit beeinträchtigt wird.
Lukas

wirklich schön!
Tania_S

aus der schnellen Dokumentation - "Protokollanforderungen mit Standardimplementierungen, die von Erweiterungen bereitgestellt werden, unterscheiden sich von optionalen Protokollanforderungen. Obwohl konforme Typen keine eigene Implementierung bereitstellen müssen, können Anforderungen mit Standardimplementierungen ohne optionale Verkettung aufgerufen werden."
Mec Os

34

Die anderen Antworten, bei denen das Protokoll als "@objc" markiert wird, funktionieren nicht, wenn schnelle spezifische Typen verwendet werden.

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"

Um optionale Protokolle zu deklarieren, die mit Swift gut funktionieren, deklarieren Sie die Funktionen als Variablen anstelle von Funktionen.

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

Implementieren Sie dann das Protokoll wie folgt

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

Sie können dann "?" um zu überprüfen, ob die Funktion implementiert wurde oder nicht

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

var anEntity: Health = returnEntity()

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

3
Mit dieser Lösung können Sie von keiner der Protokollfunktionen aus auf sich selbst zugreifen. Dies kann in einigen Fällen zu Problemen führen!
George Green

1
@ GeorgeGreen Sie können auf sich selbst zugreifen. Markieren Sie die Funktionsvariable als faul und verwenden Sie eine Erfassungsliste innerhalb des Abschlusses .
Zag

Nur Klassen, Protokolle, Methoden und Eigenschaften können @objc verwenden. Wenn Sie einen Enum-Parameter in der Definition der @ objc-Protokollmethode verwenden, sind Sie zum Scheitern verurteilt.
Khunshan

1
@khunshan, für diese Methode muss nichts mit @ objc markiert sein. Worauf beziehen Sie sich?
Zag

Es handelt sich um eine Information zu dem Thema, dass Enums nicht zwischen swift und objc verwendet werden können. Diese für andere Anweisungen können mit dem Schlüsselwort @objc überbrückt werden.
Khunshan

34

Hier ist ein konkretes Beispiel mit dem Delegierungsmuster.

Richten Sie Ihr Protokoll ein:

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

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

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

Setzen Sie den Delegaten auf eine Klasse und implementieren Sie das Protokoll. Stellen Sie sicher, dass die optionale Methode nicht implementiert werden muss.

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

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

    func requiredMethod()
    {
    }
}

Eine wichtige Sache ist, dass die optionale Methode optional ist und ein "?" beim anrufen. Erwähnen Sie das zweite Fragezeichen.

delegate?.optionalMethod?()

2
Dies sollte die richtige Antwort sein. Einfach, sauber und erklärend.
Echelon

Ich stimme zu, diese Antwort ist kurz, süß und direkt auf den Punkt. Vielen Dank!
LuAndre

31

In Swift 3.0

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

Das spart Zeit.


6
Gibt es @objcjetzt eine Quelle für das Warum aller Mitglieder?
DevAndArtist

@ Gautam Sareriya: Was ist Ihrer Meinung nach am besten für diesen Ansatz oder die Erstellung leerer Erweiterungsmethoden?
Eonist

Ich habe es mit Ihren Methoden mit requiredflag versucht , aber mit Fehlern: requireddarf nur für 'init'-Deklarationen verwendet werden.
Ben

27
  • Sie müssen optionalvor jeder Methode ein Schlüsselwort hinzufügen .
  • Beachten Sie jedoch, dass Ihr Protokoll mit dem Attribut @objc gekennzeichnet sein muss, damit dies funktioniert.
  • Dies impliziert ferner, dass dieses Protokoll auf Klassen, jedoch nicht auf Strukturen anwendbar wäre.

Kleinere Korrektur (zu klein zum Bearbeiten!), Aber es sollte 'optional' sein, nicht '@optional'
Ali Beadle

5
Dazu müssen Sie auch Ihre Klasse, die das Protokoll implementiert, als @objcnicht nur als Protokoll markieren .
Johnathon Sullinger

13

Ein reiner Swift-Ansatz mit Protokollvererbung:

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

Um die Mechanik von Antoines Antwort zu veranschaulichen:

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

Ich denke, bevor Sie fragen, wie Sie eine optionale Protokollmethode implementieren können, sollten Sie sich fragen, warum Sie eine implementieren sollten.

Wenn wir schnelle Protokolle als Schnittstelle in der klassischen objektorientierten Programmierung betrachten, sind optionale Methoden wenig sinnvoll, und eine bessere Lösung wäre möglicherweise, eine Standardimplementierung zu erstellen oder das Protokoll in eine Reihe von Protokollen zu unterteilen (möglicherweise mit einigen Vererbungsbeziehungen) zwischen ihnen), um die mögliche Kombination von Methoden im Protokoll darzustellen.

Weitere Informationen finden Sie unter https://useyourloaf.com/blog/swift-optional-protocol-methods/ , das einen hervorragenden Überblick zu diesem Thema bietet.


Diese. Es ist die Einhaltung des "I" -Prinzips in den SOLID- Softwareentwicklungsprinzipien .
Eric

7

Etwas abseits des Themas von der ursprünglichen Frage, aber es baut auf Antoines Idee auf und ich dachte, es könnte jemandem helfen.

Sie können berechnete Eigenschaften auch für Strukturen mit Protokollerweiterungen optional machen.

Sie können eine Eigenschaft für das Protokoll optional machen

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

Implementieren Sie die berechnete Dummy-Eigenschaft in der Protokollerweiterung

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

Und jetzt können Sie Strukturen verwenden, bei denen die optionale Eigenschaft implementiert ist oder nicht

struct ConformsWithoutOptional {
    let required: String
}

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

Ich habe in meinem Blog auch beschrieben, wie optionale Eigenschaften in Swift-Protokollen ausgeführt werden. Diese werden auf dem neuesten Stand gehalten, falls sich durch die Swift 2-Versionen etwas ändert.


5

So erstellen Sie optionale und erforderliche Delegierungsmethoden.

@objc protocol InterViewDelegate:class {

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

}

3

Hier ist ein sehr einfaches Beispiel NUR für schnelle Klassen und nicht für Strukturen oder Aufzählungen. Beachten Sie, dass die Protokollmethode optional ist und zwei Ebenen der optionalen Verkettung im Spiel hat. Außerdem benötigt die Klasse, die das Protokoll übernimmt, das Attribut @objc in ihrer Deklaration.

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

 }

Können Sie den Teil " Zwei Ebenen des optionalen Spiels " etwas genauer beschreiben , nämlich : delegate?.indexDidChange?(index!)?
Unheilig

2
Wenn wir das Protokoll so geschrieben hätten, dass es eine nicht optionale Methode wie diese enthält protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } , würden Sie es ohne das Fragezeichen aufrufen: delegate?.indexDidChange(index!) Wenn Sie eine optionale Anforderung für eine Methode in einem Protokoll festlegen, implementiert der Typ, der dieser Methode entspricht, diese Methode möglicherweise NICHT wird also ?verwendet, um nach der Implementierung zu suchen. Wenn keine vorhanden ist, stürzt das Programm nicht ab. @ Unheilig
Blessing Lopes

weak var delegate : CollectionOfDataDelegate?(Schwache Referenz sicherstellen?)
Alfie Hanssen

@BlessingLopes Können Sie delegate?Ihrer Antwort Ihre Erklärung zur Verwendung hinzufügen ? Diese Informationen sollten in Zukunft wirklich für andere da sein. Ich möchte dies positiv bewerten, aber diese Informationen sollten wirklich in der Antwort enthalten sein.
Johnathon Sullinger

3

Wenn Sie dies in reiner Schnelligkeit tun möchten, ist es am besten, eine Standardimplementierung bereitzustellen, insbesondere wenn Sie einen Swift-Typ wie z. B. struct zurückgeben mit Swift-Typen zurückgeben

Beispiel:

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

dann können Sie Protokoll ohne definiert jede implementieren func


2

Es gibt zwei Möglichkeiten, wie Sie eine optionale Methode im Swift-Protokoll erstellen können.

1 - Die erste Option besteht darin, Ihr Protokoll mit dem Attribut @objc zu markieren. Dies bedeutet zwar, dass es nur von Klassen übernommen werden kann, bedeutet jedoch, dass Sie einzelne Methoden wie folgt als optional markieren:

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

2 - Ein schnellerer Weg: Diese Option ist besser. Schreiben Sie Standardimplementierungen der optionalen Methoden, die so nichts tun.

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

extension MyProtocol {

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

Swift verfügt über eine Funktion namens Erweiterung, mit der wir eine Standardimplementierung für die Methoden bereitstellen können, die optional sein sollen.


0

Eine Möglichkeit besteht darin, sie als optionale Funktionsvariablen zu speichern:

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

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

-1

Definieren Sie die Funktion im Protokoll und erstellen Sie eine Erweiterung für dieses Protokoll. Erstellen Sie dann eine leere Implementierung für die Funktion, die Sie als Option verwenden möchten.


-2

Um Optional Protocolschnell zu definieren , sollten Sie das @objcSchlüsselwort vor der ProtocolDeklaration und attribute/ oder methodDeklaration innerhalb dieses Protokolls verwenden. Unten finden Sie ein Beispiel für die optionale Eigenschaft eines Protokolls.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}

2
Während dies die Frage beantworten kann, ist es besser, eine Beschreibung hinzuzufügen, wie diese Antwort zur Lösung des Problems beitragen kann. Bitte lesen Sie Wie schreibe ich eine gute Antwort , um mehr zu erfahren ?
Roshana Pitigala

-23

Stellen Sie die @optionalvor Methoden oder Eigenschaften.


Compilerfehler: Das Attribut 'optional' kann nur auf Mitglieder eines @ objc-Protokolls angewendet werden.
Selvin

3
@optionalist nicht einmal das richtige Schlüsselwort. Es ist optional, und Sie müssen die Klasse und das Protokoll mit dem @objcAttribut deklarieren .
Johnathon Sullinger
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.