Frage Wie kann ich in Swift eine Variable eines bestimmten Typs deklarieren, die einem oder mehreren Protokollen entspricht?


In Swift kann ich den Typ einer Variablen explizit festlegen, indem ich sie wie folgt deklariere:

var object: TYPE_NAME

Wenn wir einen Schritt weiter gehen und eine Variable deklarieren wollen, die mehreren Protokollen entspricht, können wir die protocol deklarativ:

var object: protocol<ProtocolOne,ProtocolTwo>//etc

Was ist, wenn ich ein Objekt deklarieren möchte, das einem oder mehreren Protokollen entspricht und auch einen bestimmten Basisklasse-Typ hat? Das Objective-C-Äquivalent würde folgendermaßen aussehen:

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;

In Swift würde ich erwarten, dass es so aussieht:

var object: TYPE_NAME,ProtocolOne//etc

Dies gibt uns die Flexibilität, mit der Implementierung des Basistyps sowie der im Protokoll definierten zusätzlichen Schnittstelle umgehen zu können.

Gibt es einen anderen offensichtlichen Weg, dass ich vermisst werde?

Beispiel

Als ein Beispiel sage ich, ich habe eine UITableViewCell Factory, die für die Rückgabe von Zellen verantwortlich ist, die einem Protokoll entsprechen. Wir können einfach eine generische Funktion einrichten, die Zellen zurückgibt, die einem Protokoll entsprechen:

class CellFactory {
    class func createCellForItem<T: UITableViewCell where T:MyProtocol >(item: SpecialItem,tableView: UITableView) -> T {
        //etc
    }
}

Später möchte ich diese Zellen aus der Warteschlange nehmen, während ich gleichzeitig den Typ und das Protokoll nutze

var cell: MyProtocol = CellFactory.createCellForItem(somethingAtIndexPath) as UITableViewCell

Dies gibt einen Fehler zurück, da eine Tabellenansichtszelle nicht dem Protokoll entspricht.

Ich möchte in der Lage sein zu spezifizieren, dass Zelle a ist UITableViewCell und entspricht dem MyProtocol in der Variablendeklaration?

Rechtfertigung

Wenn Sie mit der. Vertraut sind Fabrikmuster dies wäre sinnvoll im Kontext der Rückgabe von Objekten einer bestimmten Klasse, die eine bestimmte Schnittstelle implementieren.

Genau wie in meinem Beispiel möchten wir manchmal Schnittstellen definieren, die sinnvoll sind, wenn sie auf ein bestimmtes Objekt angewendet werden. Mein Beispiel für die Tabellenansichtszelle ist eine solche Begründung.

Während der mitgelieferte Typ nicht exakt der angegebenen Schnittstelle entspricht, wird das Objekt, das die Fabrik zurückgibt, verwendet, und daher möchte ich die Flexibilität bei der Interaktion mit dem Basisklassentyp und der deklarierten Protokollschnittstelle haben


76
2017-10-16 10:12


Ursprung


Antworten:


In Swift 4 ist es jetzt möglich, eine Variable zu deklarieren, die eine Unterklasse eines Typs ist und gleichzeitig ein oder mehrere Protokolle implementiert.

var myVariable: MyClass & MyProtocol & MySecondProtocol

oder als Parameter einer Methode:

func shakeEm(controls: [UIControl & Shakeable]) {}

Apple kündigte dies auf der WWDC 2017 in Sitzung 402: Was ist neu in Swift?

Zweitens möchte ich über das Erstellen von Klassen und Protokollen sprechen. Also, hier   Ich habe dieses shakable Protokoll für ein UI-Element eingeführt, das geben kann   ein kleiner Schütteleffekt, um die Aufmerksamkeit auf sich zu ziehen. Und ich bin gegangen   und erweiterte einige der UIKit-Klassen, um diesen Shake tatsächlich bereitzustellen   Funktionalität. Und jetzt möchte ich etwas schreiben, das einfach scheint. ich   Ich möchte nur eine Funktion schreiben, die eine Reihe von Steuerelementen enthält   erschüttern und erschüttern diejenigen, die Aufmerksamkeit erregen können   Sie. Welchen Typ kann ich hier in dieses Array schreiben? Es ist eigentlich   frustrierend und knifflig. Also könnte ich versuchen, ein UI-Steuerelement zu verwenden. Aber nicht   Alle UI-Steuerelemente sind in diesem Spiel veränderbar. Ich könnte versuchen, shakable, aber   Nicht alle Shakables sind UI-Steuerelemente. Und dazu gibt es eigentlich keinen guten Weg   repräsentiere das in Swift 3. Swift 4 führt den Begriff des Komponierens ein   eine Klasse mit einer beliebigen Anzahl von Protokollen.


42
2017-07-24 09:07



Sie können keine Variable wie deklarieren

var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...

noch deklariere Funktion Rückgabetyp wie

func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }

Sie können als Funktion Parameter wie folgt deklarieren, aber es ist im Grunde Up-Casting.

func someFunc<T:Base where T:protocol<MyProtocol1,MyProtocol2>>(val:T) {
    // here, `val` is guaranteed to be `Base` and conforms `MyProtocol` and `MyProtocol2`
}

class SubClass:BaseClass, MyProtocol1, MyProtocol2 {
   //...
}

let val = SubClass()
someFunc(val)

Ab sofort können Sie nur noch Folgendes tun:

class CellFactory {
    class func createCellForItem(item: SpecialItem) -> UITableViewCell {
        return ... // any UITableViewCell subclass
    }
}

let cell = CellFactory.createCellForItem(special)
if let asProtocol = cell as? protocol<MyProtocol1,MyProtocol2> {
    asProtocol.protocolMethod()
    cell.cellMethod()
}

Mit diesem technisch cell ist identisch mit asProtocol.

Aber wie für Compiler, cell hat Schnittstelle von UITableViewCell nur, während asProtocol hat nur Protokolle Schnittstelle. Also, wenn du anrufen willst UITableViewCellMethoden, die Sie verwenden müssen cell Variable. Wenn Sie die Protokollmethode aufrufen möchten, verwenden Sie asProtocol Variable.

Wenn Sie sicher sind, dass die Zelle den Protokollen entspricht, müssen Sie sie nicht verwenden if let ... as? ... {}. mögen:

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>

30
2017-10-16 11:51



Leider unterstützt Swift keine Konformität auf Objektebene. Es gibt jedoch eine etwas peinliche Arbeit, die Ihren Zwecken dienen kann.

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

    init<T: UIViewController>(vc: T) where T: SomeProtocol {
        self.protocol = vc
        self.viewController = vc
    }
}

Dann, wo immer Sie etwas tun müssen, was UIViewController hat, würden Sie auf den ViewController-Aspekt der Struktur zugreifen und alles, was Sie für den Protokollaspekt benötigen, würden Sie auf das .protocol verweisen.

Zum Beispiel:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

   init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol {
       self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController)
   }
}

Jetzt, wo immer Sie mySpecialViewController brauchen, um etwas mit UIViewController zu tun, referenzieren Sie einfach mySpecialViewController.viewController und wann immer Sie es brauchen, um eine Protokollfunktion auszuführen, verweisen Sie auf meinSpecialViewController.protocol.

Hoffentlich können wir mit Swift 4 in Zukunft ein Objekt mit Protokollen deklarieren. Aber fürs erste funktioniert das.

Hoffe das hilft!


2
2017-12-19 15:53



BEARBEITEN:  Ich lag falsch, aber wenn jemand anderes dieses Missverständnis liest wie ich, lasse ich diese Antwort da draußen. Das OP fragte nach der Protokollkonformität des Objekts einer gegebenen Unterklasse, und das ist eine andere Geschichte, wie die akzeptierte Antwort zeigt. Diese Antwort behandelt die Protokollkonformität für die Basisklasse.

Vielleicht irre ich mich, aber sprichst du nicht über Protokollkonformität zu den UITableCellView Klasse? Das Protokoll wird in diesem Fall auf die Basisklasse und nicht auf das Objekt erweitert. Siehe Apples Dokumentation auf Protokollannahme mit einer Erweiterung deklarieren was in Ihrem Fall wäre etwas wie:

extension UITableCellView : ProtocolOne {}

// Or alternatively if you need to add a method, protocolMethod()
extension UITableCellView : ProcotolTwo {
   func protocolTwoMethod() -> String {
     return "Compliant method"
   }
}

Zusätzlich zu der bereits erwähnten Swift-Dokumentation, siehe auch Nate Cooks Artikel Generische Funktionen für inkompatible Typen mit weiteren Beispielen.

Dies gibt uns die Flexibilität, mit der Implementierung des Basistyps sowie der im Protokoll definierten zusätzlichen Schnittstelle umgehen zu können.

Gibt es einen anderen offensichtlichen Weg, dass ich vermisst werde?

Protocol Adoption wird genau das tun, damit ein Objekt dem vorgegebenen Protokoll entspricht. Sei dir aber der negativen Seite bewusst, die eine Variable eines bestimmten Protokolltyps hat nicht weiß etwas außerhalb des Protokolls. Dies kann jedoch umgangen werden, indem ein Protokoll definiert wird, das alle benötigten Methoden / Variablen / ... enthält.

Während der mitgelieferte Typ nicht exakt der angegebenen Schnittstelle entspricht, wird das Objekt, das die Fabrik zurückgibt, verwendet, und daher möchte ich die Flexibilität bei der Interaktion mit dem Basisklassentyp und der deklarierten Protokollschnittstelle haben

Wenn Sie für eine generische Methode möchten, dass die Variable sowohl einem Protokoll als auch Basisklasse-Typen entspricht, könnten Sie Pech haben. Aber es klingt so, als müsste man das Protokoll so weit definieren, dass es die benötigten Konformitätsmethoden hat, und gleichzeitig so eng gefasst, dass man es ohne zu viel Aufwand in Basisklassen übernehmen kann (dh man deklariert einfach, dass eine Klasse konform ist) Protokoll).


1
2018-03-07 04:37



Ich hatte einmal eine ähnliche Situation, als ich versuchte, meine generischen Interaktor-Verbindungen in Storyboards zu verbinden (IB erlaubt es Ihnen nicht, Outlets mit Protokollen zu verbinden, nur Objektinstanzen), die ich einfach durch Maskieren des Basisklasse-Public-Ivars mit einem privaten Compressed umgehen konnte Eigentum. Dies verhindert zwar nicht, dass jemand per se illegale Zuweisungen vornimmt, bietet jedoch eine bequeme Möglichkeit, unerwünschte Interaktionen mit einer nicht konformen Instanz zur Laufzeit sicher zu verhindern. (d. h. den Aufruf von Delegate-Methoden für Objekte verhindern, die nicht dem Protokoll entsprechen.)

Beispiel:

@objc protocol SomeInteractorInputProtocol {
    func getSomeString()
}

@objc protocol SomeInteractorOutputProtocol {
    optional func receiveSomeString(value:String)
}

@objc class SomeInteractor: NSObject, SomeInteractorInputProtocol {

    @IBOutlet var outputReceiver : AnyObject? = nil

    private var protocolOutputReceiver : SomeInteractorOutputProtocol? {
        get { return self.outputReceiver as? SomeInteractorOutputProtocol }
    }

    func getSomeString() {
        let aString = "This is some string."
        self.protocolOutputReceiver?.receiveSomeString?(aString)
    }
}

Der "outputReceiver" wird als optional deklariert, ebenso wie der private "protocolOutputReceiver". Indem ich immer über die letztere (die berechnete Eigenschaft) auf den outputReceiver (auch als Delegat bezeichnet) zugreife, filtere ich effektiv alle Objekte heraus, die nicht dem Protokoll entsprechen. Jetzt kann ich einfach die optionale Verkettung verwenden, um sicher zum Delegatobjekt aufzurufen, ob es das Protokoll implementiert oder nicht.

Um dies auf Ihre Situation anzuwenden, können Sie den öffentlichen Ivar vom Typ "YourBaseClass" haben. (im Gegensatz zu AnyObject), und verwenden Sie die private berechnete Eigenschaft, um die Protokollkonformität zu erzwingen. FWIW.


0
2017-11-05 05:51