'ExpressibleByArrayLiteral' entspricht der Klasse und dessen Superklasse="Superklasse ist nicht in Unterklasse konvertierbar"

8

Ich möchte eine Unterklasse, hier namens MyLabel , die eine Unterklasse von UILabel ist, mit Array-Literalen instanziieren. Ich verwende dieses in meinem Framework ViewComposer , das die Erstellung von UIViews mit einem Array von Enums ermöglicht, die die Ansicht wie folgt zuordnen:

%Vor%

In dieser Frage habe ich den Anwendungsfall drastisch vereinfacht, statt zu schreiben:

%Vor%

Was ich tun möchte, ist die gleiche ExpressibleByArrayLiteral -Syntax, aber eine Unterklasse von UILabel , genannt MyLabel . Der Compiler stoppt mich jedoch, wenn ich versuche:

%Vor%

Die Instanziierung von UILabel unter Verwendung von Array-Literalen funktioniert dank der Übereinstimmung mit dem benutzerdefinierten Protokoll Makeable unten.

Ist es irgendwie möglich, dem Compiler zu verdeutlichen, dass ich mich auf den Array-Literal-Initialisierer der Unterklasse MyLabel und nicht auf dessen Oberklasse UILabel ?

beziehe

Der folgende Code mag keinen logischen Sinn ergeben, aber es ist ein minimales Beispiel, das etwas versteckt, was ich wirklich möchte:

%Vor%

Ich habe auch versucht, dem ExpressibleByArrayLiteral -Protokoll zu entsprechen, indem ich stattdessen ExpressibleByNumberArrayLiteral erweitere (ich vermute, dass die beiden Lösungen gleichwertig sind und den gleichen Code kompilieren), so:

%Vor%

Aber das hat auch nicht funktioniert. Derselbe Kompilierungsfehler tritt auf.

Ich habe einen Kommentar in den obigen großen Codeblock geschrieben, der Compiler hätte vielleicht feststellen können, auf welchen Array-Literal-Initialisierer ich hätte zurückgreifen können, wenn ich die Negation in der where -Klausel hätte verwenden können: extension Makeable where !(Self: Instantiatable)

Aber AFAIK das ist nicht möglich, dieser Code kompiliert zumindest nicht. Noch ist extension Makeable where Self != Instantiatable .

Was kann ich tun?

Ich wäre okay, wenn ich MyLabel a final class machen müsste. Aber das macht keinen Unterschied.

Bitte sagen Sie bitte, dass dies möglich ist.

    
Sajjon 06.06.2017, 14:48
quelle

2 Antworten

3

Ich habe eine Lösung mit einem Postfix-Operator gefunden.

Da ich brauche um die UIKits UILabel mit ExpressibleByArrayLiteral installieren zu können, kann ich die vorgeschlagene Lösung von murphguys nicht mit Label und SecondLabel verwenden.

Mein ursprünglicher Code funktioniert durch Hinzufügen dieses Postfix-Operators:

%Vor%

Was macht den Code kompilieren und arbeiten. Obwohl es sich ein wenig "hacky" anfühlt ...

%Vor%

Wenn Sie daran interessiert sind, warum und wie ich das nutze, können Sie sich mein Framework ViewComposer ansehen, das diese Syntax ermöglicht:

%Vor%

Aber ich wollte auch in der Lage sein, meine eigene Composable Unterklasse zu erstellen genannt MyLabel (oder nur Label ..)

%Vor%

Was früher nicht funktioniert hat, funktioniert jetzt mit dem Caret Postfix-Operator ^ , wie folgt:

%Vor%

Was für jetzt die eleganteste Lösung ist, denke ich.

    
Sajjon 11.06.2017, 11:52
quelle
5

Nachdem ich die Apple Docs gelesen hatte, dachte ich zunächst, dass dies nicht möglich sei. Ich habe jedoch einen Post hier gefunden, der auf Strings und andere Nicht-UI-Klassen angewendet wurde. Aus dem Post habe ich die Idee, dass Sie ExpressibleByArrayLiteral nicht auf eine Unterklasse durch den Ansatz der Vererbung anwenden können, verschmolzen, die wahrscheinlich ist, warum Sie den Fehler erhalten (die ich viele Male mit vielen anderen Ansätzen reproduzieren konnte).

Schließlich, indem Sie die ExpressibleByArrayLiteral-Übernahme direkt in Ihre UILabel-Unterklasse verschieben, scheint es zu funktionieren!

%Vor%

Es ergibt sich, dass wir die Expressibility für Elementtypklassen (Strings, Ints usw.) nicht einmal erben können. Sie müssen den Initialisierer dafür erneut angeben.

Mit ein paar Optimierungen habe ich auch diese anderen Methoden zum Nachdenken angewandt!

%Vor%

Im Moment kann ich die Expressivität anscheinend nicht auf einen Protokollansatz anwenden, wie Sie es getan haben. Als Workarounds scheint dies jedoch den Trick zu tun. Jetzt müssen wir nur die Initialisierer auf jede Unterklasse anwenden. Unglücklich, aber trotzdem lohnt es sich, all das zu sehen!

UPDATE, UM DIE AUSGABE MIT DEM VERLÄNGERUNGSANSATZ ZU BEANTWORTEN

Swifts Vererbung verbietet Bequemlichkeits-Inits, wenn es nicht garantieren kann, dass die Oberklasse nicht dramatisch verändert wird. Während Ihre init die Eigenschaften von UILABEL nicht ändert, unterstützt die strikte Typisierung für Erweiterungen einfach nicht die Kombination von Erforderlichkeit und Zweckmäßigkeit bei dieser Art von Initialisierer.

Ich nehme das von diesem Beitrag , der im Link enthalten war darüber übrigens:

  

Weil dies eine nicht-finale Klasse ist. Überlegen Sie, ob es eine Unterklasse gibt   zu stapeln, das seinen eigenen erforderlichen Initialisierer hatte. Wie würden Sie sicherstellen?   das init (arrayLiteral :) hat es aufgerufen? Es konnte es nicht nennen (weil es   würde nicht wissen, dass es existiert). Also entweder init (arrayLiteral :) muss   erforderlich sein (was bedeutet, dass es Teil der Hauptdeklaration sein muss)   und keine Erweiterung), oder Stack muss endgültig sein.

     

Wenn Sie es als endgültig markieren, funktioniert das wie erwartet. Wenn du es willst   subclassed, verschieben Sie es einfach aus der Erweiterung und in den Hauptkörper.

Und wenn wir uns die zwei Fehler ansehen, die Sie bekommen, einfach indem Sie versuchen, UILabel direkt auf ExpressibleByArrayLiteral zu erweitern, und nicht durch ein verschachteltes Netzwerk von Protokollen, wie Sie es tun:

%Vor%

Also zuerst. ExpressibleByArrayLiteral benötigt eine 'erforderliche' Initialisierungsmethode, um sich an sie anzupassen, und das sagt der Compiler: Sie können nicht direkt in Erweiterungen der Superklasse implementieren, die Sie anpassen möchten. Unglücklicherweise, durch diese Logik allein ... ist Ihr gewünschter Ansatz fehlerhaft.

Sekunde. Der sehr spezielle Initializer, 'init (arrayLiteral :), ist für Klassen vom Typ final. Wie in, Klassen, die Sie mit dem Schlüsselwort FINAL für den Deklarationsheader oder für die Klasse TYPES markieren (Strings sind eins, und auch Nummernklassen). UILabel ist einfach keine Abschlussklasse, um Unterklassen zu erlauben, und Sie können dies nicht ändern, ohne die Sprache zu hacken. Um nicht-final und final zu veranschaulichen, versuchen Sie die Unterklasse String , und Sie erhalten einen Fehler, da es kein class oder protocol ist. das würde Sie nicht durch den App Store sowieso bekommen. Also von DESIGN, können Sie diese Methode nicht auf UILabel selbst über eine Erweiterung verwenden.

Drei. Sie nehmen den benutzerdefinierten Protokollansatz und versuchen, ihn durch Erweiterung und Vererbung auf UILabel anzuwenden. Ich entschuldige mich, aber Swift ignoriert einfach nicht seine Sprachstruktur, einfach weil Sie einen benutzerdefinierten Code zwischen den beiden Enden der Constraint verschieben. Ihr protocol -Ansatz ist zwar elegant, verschachtelt das Problem aber nur, und adressiert es nicht. Und das liegt daran, dass es diese Initialisierungsbedingungen unabhängig von Ihrer eigenen Middleware wieder auf UILabel anwendet.

Vierte. Auf ein bisschen logischen Gedankengang hier. Wenn Sie sich die Apple Docs auf Expressibles innerhalb von XCode ansehen (die Codedatei selbst), beachten Sie, dass die Protokolle insbesondere für RawRepresentable -Klassen und -Typen ( Strings , Ints , Doubles usw.) gelten. ):

  

Für jede Aufzählung mit einem String, einer Ganzzahl oder einem Fließkomma-Rohwert   Geben Sie den Swift-Compiler automatisch ein RawRepresentable   Konformität. Wenn Sie eine eigene benutzerdefinierte Enumeration definieren, geben Sie diese ein   Rohtyp durch Angabe des Rohtyps als erstes Element in der   Enumerations Typ Vererbungsliste. Sie können auch Literale verwenden   Geben Sie Werte für einen oder mehrere Fälle an.

Alles, was die Datendarstellung als Root-Ebene von class darstellt, kann dies durch extension übernehmen. Sie können oben deutlich sehen, dass Sie, wenn Sie protocol sofort zu UILabel hinzufügen, auch das Protokoll RawRepresentable einführen.Was es nicht meiner Natur annehmen kann, auf die ich wetten möchte, ist die Quelle des Fehlers "MyLabel kann nicht in UILabel umgewandelt werden". UILabel ist keines dieser Dinge, weshalb es dieses Attribut non-final class erhält: Es ist ein UI-Element, das eine Kombination aus vielen RawRepresentables ist. Es ist also sinnvoll, dass Sie eine Klasse, die ein Ensemble von RawRepresentables ist, nicht direkt initialisieren können, denn wenn etwas Verwechslung auf der Seite init compiler-side auftritt und den von Ihnen angestrebten Raw-Typ nicht ändert, Es könnte nur die Klasseninstanz komplett korrumpieren und jeden in einen Debug-Albtraum führen.

Um den RawRepresentable Punkt zu illustrieren, den ich versuche zu machen, passiert Folgendes, wenn Sie diesen Ansatz auf String , einen RawRepresentable -konformen Typ anwenden:

%Vor%

Während ...

%Vor%

Ich werde sogar demonstrieren, wie weit die Einschränkung in Bezug auf das Hinzufügen von Expressibles auf UILabel-Unterklassen und deren Unterklassen der zweiten Schicht geht:

%Vor%

Zum Schluss. Swift ist so konzipiert. Du kannst dieses spezielle Protokoll nicht direkt darauf anwenden, weil UILabel eine Oberklasse der Sprachlevel ist und die Leute, die mit diesem Zeug aufkommen, wollen nicht, dass du so viel überreichst in UILabel . Das können Sie einfach nicht tun, da dieses Protokoll aufgrund der Art von non-final und des Protokolls selbst nicht durch Erweiterungen von UILabel superclasses angewendet werden kann. Sie sind einfach nicht kompatibel auf diese Weise. Sie können dies jedoch auf seine Unterklassen für jede Unterklasse anwenden. Das bedeutet, dass Sie den konformen Initialisierer jedes Mal erneut deklarieren müssen. Es nervt! Aber so funktioniert es.

Ich empfehle Ihre Herangehensweise, sie scheint fast den Ansatz der Erweiterung auf ein T zu bringen. Es scheint jedoch einige Dinge in Swift zu geben, die Sie einfach nicht umgehen können. Ich bin nicht der Einzige, der diese Schlussfolgerung bestätigt (überprüfe einfach die Links), daher bitte ich dich, deinen Downvote zu entfernen. Sie haben eine Lösung, die ich Ihnen gegeben habe, ich habe Ihnen die Referenzen gegeben, um meinen Punkt zu bestätigen, und ich habe Ihnen auch Code zur Verfügung gestellt, um Ihnen zu zeigen, wie Sie diese Einschränkung in der Sprache beheben können. Manchmal gibt es einfach keine Lösung für den gewünschten Ansatz. Und manchmal ist ein anderer Ansatz der einzige Weg, um Dinge zu umgehen.

    
murphguy 08.06.2017 18:18
quelle