webentwicklung-frage-antwort-db.com.de

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 noch einen Schritt weiter gehen und eine Variable deklarieren möchten, die mehreren Protokollen entspricht, können wir das Deklarativ protocol verwenden:

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 Basisklassentyp aufweist? 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, den ich vermissen könnte?

Beispiel

Angenommen, ich habe eine UITableViewCell -Factory, die für die Rückgabe von Zellen verantwortlich ist, die einem Protokoll entsprechen. Wir können leicht 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 entfernen und dabei sowohl den Typ als auch das Protokoll nutzen

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

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

Ich möchte angeben können, dass die Zelle ein UITableViewCell ist und dem MyProtocol in der Variablendeklaration entspricht.

Rechtfertigung

Wenn Sie mit dem Factory Pattern vertraut sind, ist dies sinnvoll, wenn Sie Objekte einer bestimmten Klasse zurückgeben können, 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 Tabellenzelle ist eine solche Rechtfertigung.

Der gelieferte Typ entspricht zwar nicht genau der angegebenen Schnittstelle, das Objekt, das die Factory zurückgibt, jedoch. Daher möchte ich die Flexibilität bei der Interaktion sowohl mit dem Basisklassentyp als auch mit der deklarierten Protokollschnittstelle

89
Daniel Galasko

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 hat dies auf der WWDC 2017 in Sitzung 402: Was ist neu in Swift angekündigt

Zweitens möchte ich über das Verfassen von Klassen und Protokollen sprechen. Also, hier habe ich dieses verwackelbare Protokoll für ein UI-Element eingeführt, das einen kleinen Verwacklungseffekt erzeugen kann, um die Aufmerksamkeit auf sich zu ziehen. Und ich habe einige der UIKit-Klassen erweitert, um diese Shake-Funktionalität tatsächlich bereitzustellen. Und jetzt möchte ich etwas schreiben, das einfach zu sein scheint. Ich möchte nur eine Funktion schreiben, die eine Reihe von Steuerelementen enthält, die verwackelbar sind, und die diejenigen verwackelt, die in der Lage sind, die Aufmerksamkeit auf sie zu lenken. Welchen Typ kann ich hier in dieses Array schreiben? Es ist eigentlich frustrierend und trickreich. Also könnte ich versuchen, ein UI-Steuerelement zu verwenden. Aber nicht alle UI-Steuerelemente sind in diesem Spiel verwackelbar. Ich könnte versuchen, shakable, aber nicht alle shakables sind UI-Steuerelemente. Und es gibt eigentlich keine gute Möglichkeit, dies in Swift 3. Swift 4 führt den Begriff des Komponierens einer Klasse mit einer beliebigen Anzahl von Protokollen ein.

63
Philipp Otto

Sie können keine Variablen wie deklarieren

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

noch deklarieren Funktion Rückgabetyp wie

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

Sie können einen Funktionsparameter wie diesen deklarieren, aber im Grunde handelt es sich um ein Upcasting.

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

Damit ist cell technisch identisch mit asProtocol.

Was den Compiler betrifft, hat cell nur die Schnittstelle UITableViewCell, während asProtocol nur die Schnittstelle protocols hat. Wenn Sie also die Methoden von UITableViewCell aufrufen möchten, müssen Sie die Variable cell verwenden. Wenn Sie die Methode protocols aufrufen möchten, verwenden Sie die Variable asProtocol.

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

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

Leider unterstützt Swift nicht die Protokollkonformität auf Objektebene. Es gibt jedoch eine etwas umständliche Problemumgehung, die möglicherweise Ihren Zwecken dient.

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

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

Überall dort, wo Sie etwas tun müssen, was UIViewController hat, greifen Sie auf den ViewController-Aspekt der Struktur zu, und wenn Sie den Protokollaspekt benötigen, verweisen Sie auf das Protokoll.

Zum Beispiel:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

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

Jetzt verweisen Sie immer dann, wenn Sie mySpecialViewController für UIViewController benötigen, auf mySpecialViewController.viewController, und wenn Sie eine Protokollfunktion benötigen, auf mySpecialViewController.protocol.

Hoffentlich können wir mit Swift 4 in Zukunft ein Objekt mit daran angehängten Protokollen deklarieren. Aber im Moment funktioniert das.

Hoffe das hilft!

2
Michael Curtis

EDIT: Ich habe mich geirrt, aber wenn jemand anderes dieses Missverständnis wie ich liest, lasse ich diese Antwort da draußen. Das OP fragte nach der Überprüfung der Protokollkonformität des Objekts einer bestimmten Unterklasse, und das ist eine andere Geschichte, wie die akzeptierte Antwort zeigt. Diese Antwort handelt von der Protokollkonformität für die Basisklasse.

Vielleicht irre ich mich, aber redest du nicht darüber, der Klasse UITableCellView Protokollkonformität hinzuzufügen? Das Protokoll wird in diesem Fall auf die Basisklasse und nicht auf das Objekt erweitert. Siehe Apples Dokumentation zu Declaring Protocol Adoption with a Extension was in Ihrem Fall ungefähr so ​​aussehen würde:

extension UITableCellView : ProtocolOne {}

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

Neben der bereits referenzierten 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, den ich vermissen könnte?

Protocol Adoption wird genau dies tun, um ein Objekt an das angegebene Protokoll anzupassen. Seien Sie sich jedoch der negativen Seite bewusst, dass eine Variable eines bestimmten Protokolltyps etwas außerhalb des Protokolls nicht weiß. Dies kann jedoch umgangen werden, indem ein Protokoll definiert wird, das alle erforderlichen Methoden/Variablen/... enthält.

Der gelieferte Typ entspricht zwar nicht genau der angegebenen Schnittstelle, das Objekt, das die Factory zurückgibt, jedoch. Daher möchte ich die Flexibilität bei der Interaktion sowohl mit dem Basisklassentyp als auch mit der deklarierten Protokollschnittstelle

Wenn Sie möchten, dass eine generische Methode, eine Variable, sowohl einem Protokoll- als auch einem Basisklassentyp entspricht, haben Sie möglicherweise Pech. Es hört sich jedoch so an, als müsste das Protokoll breit genug definiert werden, um die erforderlichen Konformitätsmethoden zu haben, und gleichzeitig schmal genug, um die Option zu haben, es auf Basisklassen ohne zu viel Arbeit anzuwenden (dh nur zu deklarieren, dass eine Klasse mit dem übereinstimmt Protokoll).

1
holroy

Ich hatte einmal eine ähnliche Situation, als ich versuchte, meine generischen Interaktionsverbindungen in Storyboards zu verknüpfen (IB erlaubt Ihnen nicht, Ausgänge mit Protokollen zu verbinden, nur Objektinstanzen), die ich durch einfaches Maskieren des öffentlichen ivar der Basisklasse mit einem privaten Computing 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. verhindern, dass Delegatmethoden für Objekte aufgerufen werden, 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 ebenso wie der private "protocolOutputReceiver" als optional deklariert. Indem ich immer über Letzteres (die berechnete Eigenschaft) auf den outputReceiver (einen anderen Delegaten) zugreife, filtere ich alle Objekte heraus, die nicht dem Protokoll entsprechen. Jetzt kann ich einfach die optionale Verkettung verwenden, um das Delegate-Objekt sicher darauf hinzuweisen, ob es das Protokoll implementiert oder überhaupt vorhanden ist.

Um dies auf Ihre Situation anzuwenden, können Sie die öffentliche ivar vom Typ "YourBaseClass?" (im Gegensatz zu AnyObject) und verwenden Sie die Eigenschaft private computed, um die Protokollkonformität zu erzwingen. FWIW.

0
quickthyme