Ist dies möglich in Swift? Wenn nicht, gibt es dann eine Möglichkeit, es zu umgehen?
Antworten
Zu viele Anzeigen?1. Verwendung von Standardimplementierungen (bevorzugt).
protocol MyProtocol {
func doSomething()
}
extension MyProtocol {
func doSomething() {
/* Gib einen Standardwert zurück oder lasse es einfach leer */
}
}
struct MyStruct: MyProtocol {
/* kein Kompilierfehler */
}
Vorteile
-
Es ist kein Objective-C-Laufzeitcode involviert (nun ja, zumindest nicht explizit). Das bedeutet, dass Sie Strukturen, Enums und Nicht-
NSObject
-Klassen daran anpassen können. Außerdem können Sie das leistungsstarke Generiksystem nutzen. -
Sie können immer sicher sein, dass alle Anforderungen erfüllt sind, wenn Sie auf Typen treffen, die einem solchen Protokoll entsprechen. Es handelt sich immer entweder um eine konkrete Implementierung oder eine Standardimplementierung. So verhalten sich "Interfaces" oder "Contracts" in anderen Sprachen.
Nachteile
-
Für Anforderungen, die nicht
Void
sind, benötigen Sie einen vernünftigen Standardwert, was nicht immer möglich ist. Wenn Sie jedoch auf dieses Problem stoßen, bedeutet dies entweder, dass eine solche Anforderung wirklich keine Standardimplementierung haben sollte, oder dass Sie einen Fehler bei der API-Entwicklung gemacht haben. -
Sie können nicht zwischen einer Standardimplementierung und gar keiner Implementierung unterscheiden, zumindest nicht, ohne das Problem mit speziellen Rückgabewerten anzugehen. Betrachten Sie das folgende Beispiel:
protocol SomeParserDelegate { func validate(value: Any) -> Bool }
Wenn Sie eine Standardimplementierung bereitstellen, die einfach
true
zurückgibt - scheint das auf den ersten Blick in Ordnung zu sein. Betrachten Sie jetzt den folgenden Pseudocode:final class SomeParser { func parse(data: Data) -> [Any] { if /* delegate.validate(value:) ist nicht implementiert */ { /* sehr schnell ohne Validierung parsen */ } else { /* jeden Wert parsen und validieren */ } } }
Es gibt keine Möglichkeit, eine solche Optimierung zu implementieren - Sie können nicht wissen, ob Ihr Delegierter eine Methode implementiert oder nicht.
Obwohl es verschiedene Möglichkeiten gibt, dieses Problem zu überwinden (z. B. Verwendung optionaler Closures, verschiedene Delegiertenobjekte für verschiedene Operationen zu benennen), zeigt das Beispiel das Problem deutlich.
2. Verwendung von @objc optional
.
@objc protocol MyProtocol {
@objc optional func doSomething()
}
class MyClass: NSObject, MyProtocol {
/* kein Kompilierfehler */
}
Vorteile
- Es wird keine Standardimplementierung benötigt. Sie deklarieren einfach eine optionale Methode oder eine Variable und sind bereit.
Nachteile
-
Es begrenzt die Fähigkeiten Ihres Protokolls erheblich, da alle konformen Typen kompatibel mit Objective-C sein müssen. Das bedeutet, dass nur Klassen, die von
NSObject
erben, ein solches Protokoll einhalten können. Keine Strukturen, keine Enums, keine assoziierten 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 viel Boilerplate-Code einführen, wenn Sie oft optionale Methoden aufrufen.
In Swift 2 und später ist es möglich, Standardimplementierungen eines Protokolls hinzuzufügen. Dies schafft eine neue Möglichkeit für optionale Methoden in Protokollen.
protocol MeinProtokoll {
func tueEtwasNichtOptionaleMethode()
func tueEtwasOptionaleMethode()
}
extension MeinProtokoll {
func tueEtwasOptionaleMethode(){
// Dies bleibt leer
}
}
Es ist nicht wirklich eine schöne Möglichkeit, optionale Protokollmethoden zu erstellen, aber es gibt Ihnen 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/
Hier ist ein konkretes Beispiel mit dem Delegationsmuster.
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 Delegierten auf eine Klasse und implementieren Sie das Protokoll. Beachten Sie, dass die optionale Methode nicht implementiert werden muss.
class AnotherClass: NSObject, MyProtocol
{
init()
{
super.init()
let myInstance = MyClass()
myInstance.delegate = self
}
func requiredMethod()
{
}
}
Ein wichtiger Punkt ist, dass die optionale Methode optional ist und ein "?" beim Aufrufen benötigt. Erwähnen Sie das zweite Fragezeichen.
delegate?.optionalMethod?()
Da einige Antworten darüber informieren, wie man das optionale Modifier und das @objc Attribut verwendet, um eine optionale Anforderungs-Protokoll zu definieren, werde ich ein Beispiel geben, wie man mit Protokoll-Erweiterungen ein optionales Protokoll definiert.
Der untenstehende Code ist Swift 3.*.
/// Protokoll hat eine leere Standardimplementierung der folgenden Methoden, die optional zu implementieren sind:
/// `cancel()`
protocol Cancelable {
/// Standardimplementierung ist leer.
func cancel()
}
extension Cancelable {
func cancel() {}
}
class Plane: Cancelable {
//Da cancel() eine Standardimplementierung hat, ist diese optional für die Klasse Plane
}
let plane = Plane()
plane.cancel()
// Gibt *United Airlines kann nicht stornierbar* aus
Bitte beachten Sie, dass Methoden von Protokollerweiterungen nicht von Objective-C-Code aufgerufen werden können, und es ist sogar noch schlimmer, dass das Swift-Team es nicht beheben wird. https://bugs.swift.org/browse/SR-492
Die anderen Antworten hier, die das Markieren des Protokolls als "@objc" beinhalten, funktionieren nicht, wenn man swift-spezifische Typen verwendet.
struct Info {
var height: Int
var weight: Int
}
@objc protocol Health {
func isInfoHealthy(info: Info) -> Bool
}
//Fehler "Methode kann nicht mit @objc markiert werden, weil der Typ des Parameters nicht in Objective-C dargestellt werden kann"
Um optionale Protokolle zu deklarieren, die gut mit Swift funktionieren, deklarieren Sie die Funktionen als Variablen anstelle von Funktionen.
protocol Health {
var isInfoHealthy: (Info) -> (Bool)? { get set }
}
Und implementieren Sie das Protokoll dann wie folgt
class Human: Health {
var isInfoHealthy: (Info) -> (Bool)? = { info in
if info.weight < 200 && info.height > 72 {
return true
}
return false
}
//Oder lassen Sie die Implementierung weg und deklarieren Sie es als:
//var isInfoHealthy: (Info) -> (Bool)?
}
Sie können dann "?" verwenden, um zu überprüfen, ob die Funktion implementiert wurde
func returnEntity() -> Health {
return Human()
}
var anEntity: Health = returnEntity()
var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))?
//"isHealthy" ist true
- See previous answers
- Weitere Antworten anzeigen