Jeder Ingenieur ist bestrebt, seinen Arbeitsprozess so optimiert wie möglich zu gestalten. Als Entwickler von mobilen iOS müssen wir sehr oft mit einheitlichen Sprachstrukturen arbeiten. Apple verbessert die Entwicklertools, indem es große Anstrengungen unternimmt, um das Programmieren für uns bequemer zu machen: Hervorheben von Sprachen, Methoden zur automatischen Vervollständigung und viele andere IDE-Funktionen, mit denen unsere Finger mit den Ideen in unseren Köpfen Schritt halten können.

Was macht ein Ingenieur, wenn das erforderliche Werkzeug fehlt? Es stimmt, er wird alles selbst machen! Früher haben wir über das Erstellen unserer benutzerdefinierten Tools gesprochen. Jetzt wollen wir
darüber sprechen, wie Sie Xcode ändern und es gemäß Ihren Regeln funktionieren lassen.
Wir haben die Aufgabe von
JIRA Swift übernommen und ein Tool erstellt, das konvertiert,
wenn es in ein gleichwertiges
Guard-Let- Konstrukt
eingelassen wird .

Seit der neunten Version bietet Xcode einen neuen Refactoring-Mechanismus, mit dem Code lokal, in derselben Swift-Quelldatei oder global konvertiert werden kann, wenn Sie eine Methode oder Eigenschaft umbenennen, die in mehreren Dateien vorkommt, auch wenn diese in verschiedenen Sprachen vorliegen.
Das lokale Refactoring ist vollständig im SourceKit-Compiler und -Framework implementiert. Die Funktion befindet sich im Open Source-Swift-Repository und ist in C ++ geschrieben. Änderungen des globalen Refactorings sind derzeit für normale Benutzer nicht zugänglich, da die Xcode-Codebasis geschlossen ist. Deshalb werden wir uns mit der lokalen Geschichte befassen und darüber sprechen, wie wir unsere Erfahrungen wiederholen können.
Was Sie benötigen, um Ihr eigenes Tool für das lokale Refactoring zu erstellen:
- C ++ verstehen
- Grundkenntnisse des Compilers
- Verstehen, was AST ist und wie man damit arbeitet
- Schneller Quellcode
- Anleitung swift / docs / refactoring / SwiftLocalRefactoring.md
- Viel Geduld
Ein bisschen über AST
Ein paar theoretische Grundlagen, bevor Sie in die Praxis eintauchen. Schauen wir uns an, wie die Swift-Compiler-Architektur funktioniert. Zunächst ist der Compiler dafür verantwortlich, den Code in ausführbaren Maschinencode umzuwandeln.

Von den vorgestellten Transformationsstufen ist für uns die Erzeugung eines abstrakten Syntaxbaums (AST) am interessantesten - ein Diagramm, in dem die Eckpunkte Operatoren und die Blätter ihre Operanden sind.

Syntaxbäume werden im Parser verwendet. AST wird als interne Darstellung im Compiler / Interpreter eines Computerprogramms zur Optimierung und Generierung von Code verwendet.
Nachdem der AST generiert wurde, wird eine Analyse durchgeführt, um den AST mit einer Typprüfung zu erstellen, die in die Swift Intermediate Language übersetzt wurde. SIL wird konvertiert, optimiert und auf LLVM IR heruntergestuft, das letztendlich in Maschinencode kompiliert wird.
Um ein Refactoring-Tool zu erstellen, müssen wir AST verstehen und damit arbeiten können. So kann das Tool mit Teilen des Codes, die wir verarbeiten möchten, korrekt arbeiten.
Führen Sie den folgenden Befehl aus, um den AST einer Datei zu generieren: swiftc -dump-ast
MyFile.swift
Unten finden Sie die Ausgabe der zuvor erwähnten if let- Funktion an die AST- Konsole .

In Swift AST gibt es drei Hauptknotentypen:
- Deklarationen (Unterklassen vom Typ Decl),
- Ausdrücke (Unterklassen vom Typ Ausdruck),
- Operatoren (Unterklassen vom Typ Stmt).
Sie entsprechen den drei Entitäten, die in der Swift-Sprache selbst verwendet werden. Namen von Funktionen, Strukturen, Parametern sind Deklarationen. Ausdrücke sind Entitäten, die einen Wert zurückgeben. Zum Beispiel Funktionen aufrufen. Operatoren sind Teile der Sprache, die den Kontrollfluss der Codeausführung definieren, aber keinen Wert zurückgeben (z. B. if oder do-catch).
Dies ist ein ausreichendes Minimum, das Sie für Ihre bevorstehende Arbeit über AST wissen müssen.
Wie das Refactoring-Tool theoretisch funktioniert
Um Refactoring-Tools zu implementieren, benötigen Sie spezifische Informationen zum Bereich des Codes, den Sie ändern möchten. Entwickler erhalten zusätzliche Entitäten, die Daten sammeln. Das erste, ResolvedCursorInfo (Cursor-basiertes Refactoring), sagt Ihnen, ob wir am Anfang eines Ausdrucks stehen. In diesem Fall wird das entsprechende Compilerobjekt dieses Ausdrucks zurückgegeben. Die zweite Entität, RangeInfo (bereichsbasiertes Refactoring), kapselt Daten über den ursprünglichen Bereich (z. B. wie viele Ein- und Ausstiegspunkte es hat).
Das Cursor-basierte Refactoring wird durch die Position des Cursors in der Quelldatei initiiert. Refactoring-Aktionen implementieren die Methoden, die der Refactoring-Mechanismus verwendet, um verfügbare Aktionen in der IDE anzuzeigen und Transformationen durchzuführen. Beispiele für Aktionen, die auf dem Cursor basieren: Zur Definition springen, schnelle Hilfe usw.

Betrachten Sie die üblichen Maßnahmen von der technischen Seite:
- Wenn Sie im Xcode-Editor einen Speicherort auswählen, wird eine Anforderung an sourcekitd (das Framework, das für das Hervorheben, die Code-Vervollständigung usw. verantwortlich ist) gesendet , um die verfügbaren Refactoring-Aktionen anzuzeigen.
- Jede verfügbare Aktion wird vom ResolvedCursorInfo-Objekt angefordert, um zu überprüfen, ob diese Aktion für den ausgewählten Code gilt.
- Die Liste der anwendbaren Aktionen wird als Antwort von sourcekitd zurückgegeben und in Xcode angezeigt.
- Xcode wendet die Änderungen dann auf das Refactoring-Tool an.
Das bereichsbasierte Refactoring wird durch Auswahl eines kontinuierlichen Codebereichs in der Quelldatei eingeleitet.

In diesem Fall durchläuft das Refactoring-Tool eine ähnliche beschriebene Aufrufkette. Der Unterschied besteht darin, dass bei der Implementierung die Eingabe RangeInfo anstelle von ResolvedCursorInfo lautet. Interessierte Leser finden in
Refactoring.cpp weitere Informationen zu Apple Toolkit-Beispielen.
Und nun zur Praxis, ein Werkzeug zu erstellen.
Vorbereitung
Zunächst müssen Sie den Swift-Compiler herunterladen und erstellen. Detaillierte Anweisungen finden Sie im offiziellen Repository (
readme.md ). Hier sind die wichtigsten Befehle zum Klonen von Code:
mkdir swift-source cd swift-source git clone https:
Mit Cmake werden die Struktur und die Abhängigkeiten des Projekts beschrieben. Mithilfe eines der folgenden Befehle können Sie ein Projekt für Xcode (bequemer) oder für Ninja (schneller) erstellen:
./utils/build-script --debug --xcode
oder
swift/utils/build-script --debug-debuginfo
Für eine erfolgreiche Kompilierung ist die neueste Version von Xcode Beta (10.2.1 zum Zeitpunkt des Schreibens) erforderlich - verfügbar auf der
offiziellen Apple-Website . Um den neuen Xcode zum Erstellen des Projekts zu verwenden, müssen Sie den Pfad mit dem Dienstprogramm xcode-select registrieren:
sudo xcode-select -s /Users/username/Xcode.app
Wenn wir das Flag --xcode verwendet haben, um das Projekt für Xcode zu erstellen, finden wir nach mehreren Stunden Kompilierung (wir haben etwas mehr als zwei) im Build-Ordner die Datei Swift.xcodeproj. Beim Öffnen des Projekts sehen wir den bekannten Xcode mit Indizierung und Haltepunkten.
Um ein neues Instrument zu erstellen, müssen wir den Code mit der Logik des Instruments zur Datei hinzufügen: lib / IDE / Refactoring.cpp und zwei Methoden definieren, isApplicable und performChange. Bei der ersten Methode entscheiden wir, ob die Refactoring-Option für den ausgewählten Code ausgegeben werden soll. Und im zweiten - wie man den ausgewählten Code konvertiert, um Refactoring anzuwenden.
Nach der Vorbereitung müssen noch die folgenden Schritte ausgeführt werden:
- Entwickeln der Werkzeuglogik (Die Entwicklung kann auf verschiedene Arten erfolgen - über die Toolchain, über Ninja, über Xcode; alle Optionen werden unten beschrieben.)
- Implementieren Sie zwei Methoden: isApplicable und performChange (sie sind für den Zugriff auf das Tool und dessen Betrieb verantwortlich).
- Diagnostizieren und testen Sie das fertige Tool, bevor Sie die PR an das offizielle Swift-Repository senden.
Testen Sie den Werkzeugbetrieb über die Werkzeugkette
Diese Entwicklungsmethode nimmt aufgrund der langen Montage der Komponenten viel Zeit in Anspruch, aber das Ergebnis ist in Xcode sofort sichtbar - die Möglichkeit, es manuell zu überprüfen.
Lassen Sie uns zunächst die Swift-Toolchain mit dem folgenden Befehl erstellen:
./utils/build-toolchain some_bundle_id
Das Kompilieren der Toolchain dauert noch länger als das Kompilieren des Compilers und der Abhängigkeiten. Die Ausgabe ist die Datei swift-LOCAL-yyyy-mm-dd.xctoolchain im Ordner swift-nightly-install, die Sie in Xcode übertragen müssen: / Library / Developer / Toolchains /. Wählen Sie als Nächstes in den IDE-Einstellungen die neue Toolchain aus und starten Sie Xcode neu.

Wählen Sie einen Code aus, den das Tool verarbeiten soll, und suchen Sie im Kontextmenü nach dem Tool.
Entwicklung durch Tests mit Ninja
Wenn das Projekt für Ninja erstellt wurde und Sie den TDD-Pfad gewählt haben, ist die Entwicklung durch Tests mit Ninja eine der Optionen, die zu Ihnen passen. Nachteile - Sie können keine Haltepunkte setzen, wie bei der Entwicklung über Xcode.
Wir müssen also überprüfen, ob das neue Tool in Xcode angezeigt wird, wenn der Benutzer das Schutzkonstrukt im Quellcode auswählt. Wir schreiben den Test in die vorhandene Datei test / refactoring / RefactoringKind / basic.swift:
func testConvertToGuardExpr(idxOpt: Int?) { if let idx = idxOpt { print(idx) } }
Wir weisen darauf hin, dass beim Hervorheben von Code zwischen 266 Spalten in Zeile 3 und 268 Spalten in Zeile 4 das Erscheinen eines Menüelements mit einem neuen Werkzeug erwartet wird.
Die Verwendung des Skripts
lit.py kann ein schnelleres Feedback zu Ihrem Entwicklungszyklus liefern. Sie können den gewünschten Testanzug angeben. In unserem Fall ist diese Suite RefactoringKind:
./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/
Infolgedessen werden nur Tests dieser Datei gestartet. Ihre Implementierung wird einige zehn Sekunden dauern. Weitere Informationen zu lit.py werden später im Abschnitt Diagnose und Test erläutert.
Der Test schlägt fehl, was für das TDD-Paradigma normal ist. Schließlich haben wir bisher keine einzige Codezeile mit der Logik des Tools geschrieben.
Entwicklung durch Debugging und Xcode
Und schließlich die letzte Entwicklungsmethode, als das Projekt unter Xcode erstellt wurde. Das Hauptvorteil ist die Möglichkeit, Haltepunkte festzulegen und das Debuggen zu steuern.
Beim Erstellen eines Projekts unter Xcode wird die Datei Swift.xcodeproj im Ordner build / Xcode-DebugAssert / swift-macosx-x86_64 / erstellt. Wenn Sie diese Datei zum ersten Mal öffnen, ist es besser, Schemata manuell zu erstellen, um ALL_BUILD zu generieren und sich schnell umzugestalten:

Als nächstes erstellen wir das Projekt einmal mit ALL_BUILD, danach verwenden wir das Swift-Refactor-Schema.
Das Refactoring-Tool wird in eine separate ausführbare Datei kompiliert - Swift-Refactor. Die Hilfe zu dieser Datei kann mit dem Flag –help angezeigt werden. Die interessantesten Parameter für uns sind:
-source-filename=<string>
Sie können im Schema als Argumente angegeben werden. Jetzt können Sie Haltepunkte festlegen, die beim Starten des Tools an interessanten Stellen anhalten. In der üblichen Weise werden mit den Befehlen
p und
po in der Xcode-Konsole die Werte der entsprechenden Variablen angezeigt.

IsApplicable Implementierung
Die Methode isApplicable akzeptiert eine ResolvedRangeInfo mit Informationen zu AST-Knoten des ausgewählten Codefragments an der Eingabe. Bei der Ausgabe der Methode wird entschieden, ob das Tool im Xcode-Kontextmenü angezeigt werden soll oder nicht. Die vollständige ResolvedRangeInfo-Schnittstelle finden Sie in der
Datei include / swift / IDE / Utils.h .
Betrachten Sie die Felder der ResolvedRangeInfo-Klasse, die in unserem Fall am nützlichsten sind:
- RangeKind - Überprüfen Sie zunächst den Typ des ausgewählten Bereichs. Wenn der Bereich ungültig (ungültig) ist, können Sie false zurückgeben. Wenn der Typ zu uns passt, z. B. SingleStatement oder MultiStatement, fahren Sie fort.
- ContainedNodes - Ein Array von AST-Elementen, die in den ausgewählten Bereich fallen. Wir möchten sicherstellen, dass der Benutzer den Bereich auswählt, in den das if let-Konstrukt eintritt. Dazu nehmen wir das erste Element des Arrays und prüfen, ob dieses Element IfStmt entspricht (der Klasse, die den AST-Knoten des Anweisungsknotens des if-Subtyps definiert). Als nächstes siehe Bedingung. Um die Implementierung zu vereinfachen, geben wir das Tool nur für Ausdrücke mit einer Bedingung aus. Durch die Art der Bedingung (CK_PatternBinding) bestimmen wir, dass dies vermietet ist.

Fügen Sie zum Testen von isApplicable den Beispielcode zur Datei
test / refactoring / RefactoringKind / basic.swift hinzu .

Damit der Test einen Aufruf unseres Tools simulieren kann, müssen Sie eine Zeile in die Datei
tools / swift-refactor / swift-refactor.cpp einfügen.

Wir implementieren performChange
Diese Methode wird aufgerufen, wenn im Kontextmenü ein Refactoring-Tool ausgewählt wird. Die Methode hat Zugriff auf ResolvedRangeInfo sowie auf isApplicable. Wir verwenden ResolvedRangeInfo und schreiben die Logik des Code-Konvertierungstools.
Beim Generieren von Code für statische Token (reguliert durch die Sprachsyntax) können Sie Entitäten aus dem Token-Namespace verwenden. Verwenden Sie beispielsweise für das Schlüsselwort guard tok :: kw_guard. Für dynamische Token (vom Entwickler geändert, z. B. den Namen der Funktion) müssen Sie sie aus dem Array der AST-Elemente auswählen.
Um festzustellen, wo der konvertierte Code eingefügt wird, verwenden wir den vollständig ausgewählten Bereich mithilfe des RangeInfo.ContentRange-Konstrukts.

Diagnose und Prüfung
Bevor Sie mit der Arbeit an einem Werkzeug fertig sind, müssen Sie die Richtigkeit seiner Arbeit erneut überprüfen. Die Tests werden uns wieder helfen. Tests können einzeln oder mit allen verfügbaren Bereichen ausgeführt werden. Der einfachste Weg, die gesamte Swift-Testsuite auszuführen, ist der Befehl --test für utils / build-script, mit dem die Haupttestsuite ausgeführt wird. Durch die Verwendung von utils / build-script werden alle Ziele neu erstellt, was die Debugging-Zykluszeit erheblich verlängern kann.
Stellen Sie sicher, dass Sie die Validierungstests utils / build-script --validation-test ausführen, bevor Sie größere Änderungen am Compiler oder der API vornehmen.
Es gibt eine andere Möglichkeit, alle Komponententests des Compilers auszuführen - über Ninja, Ninja Check-Swift von Build / Preset / Swift-Macosx-X86_64. Es dauert ungefähr 15 Minuten.
Und schließlich die Option, wenn Sie Tests separat ausführen müssen. Um das lit.py-Skript direkt von LLVM aus aufzurufen, müssen Sie es für die Verwendung des lokalen Build-Verzeichnisses konfigurieren. Zum Beispiel:
% $ {LLVM_SOURCE_ROOT} /utils/lit/lit.py -sv $ {SWIFT_BUILD_DIR} / test-macosx-x86_64 / Parse /
Dadurch werden die Tests im Verzeichnis 'test / Parse /' für 64-Bit-MacOS ausgeführt. Die Option -sv bietet einen Indikator für die Testausführung und zeigt nur die Ergebnisse fehlgeschlagener Tests an.
Lit.py bietet einige weitere nützliche Funktionen, z. B. Timing-Tests und Latenztests. Sie können diese und andere Funktionen mit lit.py -h anzeigen. Die nützlichsten finden Sie
hier .
Um einen Test auszuführen, schreiben Sie:
./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/basic.swift
Wenn wir die neuesten Compileränderungen abrufen müssen, müssen wir alle Abhängigkeiten aktualisieren und eine Neubasis durchführen. Führen Sie zum Aktualisieren ./utils/update-checkout aus.
Schlussfolgerungen
Wir haben es geschafft, unser Ziel zu erreichen - ein Tool zu entwickeln, das zuvor nicht in der IDE enthalten war, um die Arbeit zu optimieren. Wenn Sie auch Ideen haben, wie Sie Apple-Produkte verbessern und der gesamten iOS-Community das Leben erleichtern können, können Sie das Counter-Branding in Angriff nehmen, da dies einfacher ist, als es auf den ersten Blick scheint!
Im Jahr 2015 hat Apple den Swift-Quellcode öffentlich zugänglich gemacht, wodurch es möglich wurde, in die Implementierungsdetails seines Compilers einzutauchen. Darüber hinaus können Sie mit Xcode 9 lokale Refactoring-Tools hinzufügen. Grundkenntnisse in C ++ und einem Compiler-Gerät reichen aus, um Ihre Lieblings-IDE ein wenig komfortabler zu gestalten.
Die beschriebene Erfahrung war für uns nützlich - zusätzlich zur Erstellung eines Tools, das den Entwicklungsprozess vereinfacht, haben wir wirklich Hardcore-Kenntnisse der Sprache erhalten. Eine leicht geöffnete Büchse der Pandora mit Prozessen auf niedriger Ebene ermöglicht es Ihnen, die täglichen Aufgaben aus einem neuen Blickwinkel zu betrachten.
Wir hoffen, dass das gewonnene Wissen auch Ihr Verständnis für die Entwicklung bereichert!
Das Material wurde gemeinsam mit
@victoriaqb - Victoria Kashlina, iOS-Entwicklerin, geschrieben.
Quellen
- Schnelles Compiler-Gerät. Teil 2
- Wie erstelle ich ein schnelles Compiler-basiertes Tool? Die Schritt-für-Schritt-Anleitung
- Dumping des Swift AST für ein iOS-Projekt
- Einführung in den Sourcekitd Stress Tester
- Schnelles Testen
- [SR-5744] Refactoring-Aktion zum Konvertieren von If-Let in Guard-Let und umgekehrt # 24566