Articles

Wie Sie Ihre Xcode-Plugins in Xcode-Erweiterungen umwandeln

von Khoa Pham

Quelle: Imgur

Xcode ist eine unverzichtbare IDE für iOS- und macOS-Entwickler. Von Anfang an hat die Möglichkeit, eigene Plugins zu erstellen und zu installieren, die Produktivität enorm gesteigert. Es dauerte nicht lange, bis Apple aufgrund von Datenschutzbedenken Xcode-Erweiterungen einführte.

Ich habe einige Xcode-Plugins und -Erweiterungen wie XcodeWay, XcodeColorSense, XcodeColorSense2 und Xmas entwickelt. Es war eine lohnende Erfahrung. Ich habe viel gelernt, und die Produktivität, die ich gewonnen habe, war beträchtlich. In diesem Beitrag erkläre ich, wie ich meine Xcode-Plugins in Erweiterungen umgewandelt habe und welche Erfahrungen ich dabei gemacht habe.

Mein erstes Xcode-Plugin: XcodeWay

Ich wähle eine faule Person, um eine schwere Aufgabe zu erledigen. Denn eine faule Person wird einen einfachen Weg finden, sie zu erledigen

Ich mag das obige Zitat von Bill Gates sehr. Ich versuche, sich wiederholende und langweilige Aufgaben zu vermeiden. Wenn ich mich dabei ertappe, dass ich immer wieder die gleichen Aufgaben erledige, schreibe ich Skripte und Tools, um das zu automatisieren. Das kostet zwar etwas Zeit, aber in naher Zukunft werde ich etwas fauler sein.

Neben dem Interesse an der Entwicklung von Open-Source-Frameworks und -Tools erweitere ich gerne die IDE, die ich verwende – meistens Xcode.

Ich habe 2014 mit der iOS-Entwicklung begonnen. Ich wollte einen schnellen Weg, um zu vielen Orten direkt aus Xcode mit dem Kontext des aktuellen Projekts zu navigieren. Es kommt oft vor, dass man:

  • den aktuellen Projektordner im „Finder“ öffnen möchte, um einige Dateien zu ändern
  • Terminal öffnen, um einige Befehle auszuführen
  • die aktuelle Datei in GitHub öffnen möchte, um schnell den Link an einen Arbeitskollegen weiterzugeben
  • oder andere Ordner wie Themes, Plugins, Codeschnipsel, Geräteprotokolle öffnen möchte.

Jedes bisschen Zeit, das wir jeden Tag sparen, zählt.

Ich dachte, es wäre eine coole Idee, ein Xcode-Plugin zu schreiben, mit dem wir alle oben genannten Dinge direkt in Xcode erledigen können. Anstatt darauf zu warten, dass andere es tun, habe ich meine Ärmel hochgekrempelt und mein erstes Xcode-Plugin geschrieben – XcodeWay – und es als Open Source zur Verfügung gestellt.

XcodeWay funktioniert, indem es ein Menü unter Editor mit vielen Optionen erstellt, um direkt von Xcode aus zu anderen Orten zu navigieren. Es sieht einfach aus, aber es war einige harte Arbeit erforderlich.

Was sind Xcode Plugins?

Xcode Plugins werden nicht offiziell von Xcode unterstützt oder von Apple empfohlen. Es gibt keine Dokumente über sie. Die besten Orte, an denen man etwas über sie erfahren kann, sind der Quellcode bestehender Plugins und einige Tutorials.

Ein Xcode-Plugin ist einfach ein Bundle vom Typ xcplugin und befindet sich auf ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins. Beim Starten von Xcode werden alle Xcode-Plugins, die sich in diesem Ordner befinden, geladen. Plugins werden im selben Prozess wie Xcode ausgeführt, können also alles tun, was Xcode auch tut. Ein Fehler in einem Plugin kann Xcode zum Absturz bringen.

Um ein Xcode-Plugin zu erstellen, erstellen Sie ein macOS Bundle mit einer Klasse, die von NSObject erweitert, und haben einen Initialisierer, der NSBundle akzeptiert, zum Beispiel in Xmas:

class Xmas: NSObject {
 var bundle: NSBundle
 init(bundle: NSBundle) { self.bundle = bundle super.init() }}

Inside Info.plist, müssen wir:

  • diese Klasse als Haupteinstiegsklasse für das Plugin deklarieren, und
  • dass dieses Bundle kein UI hat, weil wir während der Laufzeit UI-Steuerelemente erstellen und der Xcode-Oberfläche hinzufügen
<key>NSPrincipalClass</key><string>Xmas</string><key>XCPluginHasUI</key><false/>

Ein weiteres Problem mit Xcode-Plugins ist, dass wir ständig DVTPluginCompatibilityUUIDsaktualisieren müssen. Dies ändert sich jedes Mal, wenn eine neue Version von Xcode herauskommt. Ohne Aktualisierung weigert sich Xcode, das Plugin zu laden.

Was Xcode-Plugins können

Viele Entwickler erstellen Xcode-Plugins, weil sie bestimmte Funktionen in anderen IDEs wie Sublime Text, AppCode oder Atom vermissen.

Da Xcode-Plugins im selben Prozess wie Xcode geladen werden, können sie alles tun, was Xcode kann. Die einzige Grenze ist unsere Vorstellungskraft. Wir können die Objective C Runtime nutzen, um private Frameworks und Funktionen zu entdecken. Dann können LLDB und symbolische Haltepunkte weiter verwendet werden, um laufenden Code zu untersuchen und sein Verhalten zu ändern. Wir können auch Swizzling verwenden, um die Implementierung eines beliebigen laufenden Codes zu ändern. Das Schreiben von Xcode-Plugins ist schwierig – man muss viel raten und manchmal sind gute Assembler-Kenntnisse erforderlich.

Im goldenen Zeitalter der Plugins gab es einen beliebten Plugin-Manager, der selbst ein Plugin war, namens Alcatraz. Es konnte andere Plugins installieren, was im Grunde nur die xcplugin Datei herunterlädt und diese in den Plug Ins Ordner verschiebt.

Um ein Gefühl dafür zu bekommen, was Plugins tun können, lassen Sie uns einen Blick auf einige populäre Plugins werfen.

Xvim

Der erste in der Liste ist Xvim, der Vim-Tastaturbindungen direkt in Xcode hinzufügt. Es unterstützt fast alle Tastenkombinationen, die wir früher in Terminal hatten.

SCXcodeMiniMap

Wenn Sie den MiniMap-Modus in Sublime Text vermissen, können Sie SCXcodeMiniMap verwenden, um ein rechtes Kartenfeld im Xcode-Editor hinzuzufügen.

FuzzyAutocompletePlugin

Vor Version 9 gab es in Xcode keine richtige Autovervollständigung – sie basierte nur auf dem Präfix. Das war der Punkt, an dem FuzzyAutocompletePlugin glänzte. Es führt eine unscharfe Autovervollständigung durch, die auf der versteckten IDEOpenQuicklyPattern-Funktion in Xcode basiert.

KSImageNamed-Xcode

Um ein Bundle-Bild innerhalb von UIImageView anzuzeigen, verwenden wir oft die imageNamed-Methode. Aber es ist schwer, sich den genauen Namen der Bilddatei zu merken. KSImageNamed-Xcode ist hier, um zu helfen. Sie erhalten eine Liste mit automatisch vorgeschlagenen Bildnamen, wenn Sie mit der Eingabe beginnen.

ColorSense-for-Xcode

Ein weiteres Problem bei der Entwicklung ist die Arbeit mit UIColor, das den RGBA-Farbraum verwendet. Wir erhalten keinen visuellen Indikator für die Farbe, die wir angeben, und die manuelle Überprüfung kann zeitaufwendig sein. Zum Glück gibt es ColorSense für Xcode, das die verwendete Farbe anzeigt und die Farbauswahl erleichtert.

LinkedConsole

In AppCode können wir zu einer bestimmten Zeile in der Datei springen, die in der Konsole aufgezeichnet wird. Wenn Sie diese Funktion in Xcode vermissen, können Sie LinkedConsole verwenden. Dies ermöglicht klickbare Links in der Xcode-Konsole, so dass wir sofort zu dieser Datei springen können.

Die harte Arbeit hinter Xcode-Plugins

Ein Xcode-Plugin zu erstellen ist nicht einfach. Wir müssen uns nicht nur mit der macOS-Programmierung auskennen, sondern auch tief in die Xcode-Ansichtshierarchie eintauchen. Wir müssen private Frameworks und APIs erforschen, um die gewünschte Funktion einzubauen.

Es gibt nur wenige Anleitungen zur Erstellung von Plugins, aber zum Glück sind die meisten Plugins Open Source, sodass wir verstehen können, wie sie funktionieren. Da ich ein paar Plugins erstellt habe, kann ich einige technische Details darüber geben.

Xcode Plugins werden normalerweise mit zwei privaten Frameworks erstellt: DVTKit und IDEKit . Die System-Frameworks sind unter /System/Library/PrivateFrameworks zu finden, aber die Frameworks, die Xcode exklusiv verwendet, sind unter /Applications/Xcode.app/Contents/ zu finden, also Frameworks , OtherFrameworks und SharedFrameworks.

Es gibt ein Tool class-dump, das Header aus dem Xcode-App-Bundle generieren kann. Mit den Klassennamen und Methoden können Sie NSClassFromString aufrufen, um die Klasse aus dem Namen zu erhalten.

Swizzling DVTBezelAlertPanel framework in Xmas

Weihnachten hat mir immer ein besonderes Gefühl gegeben, also habe ich beschlossen, Xmas zu machen, das ein zufälliges Weihnachtsbild anstelle der Standard-Alarmansicht zeigt. Die Klasse, die zum Rendern dieser Ansicht verwendet wird, ist DVTBezelAlertPanel innerhalb des DVTKit-Frameworks. Mein Artikel über die Erstellung dieses Plugins ist hier.

Bei Objective C Runtime gibt es eine Technik namens Swizzling, mit der die Implementierung und die Methodensignatur beliebiger laufender Klassen und Methoden geändert und ausgetauscht werden können.

Hier müssen wir, um den Inhalt dieser Alarmansicht zu ändern, den Initialisierer initWithIcon:message:parentWindow:duration: mit unserer eigenen Methode austauschen. Wir tun dies, indem wir auf NSApplicationDidFinishLaunchingNotification hören, das benachrichtigt wird, wenn ein macOS-Plugin, in diesem Fall Xcode, startet.

class func swizzleMethods() { guard let originalClass = NSClassFromString("DVTBezelAlertPanel") as? NSObject.Type else { return }
do { try originalClass.jr_swizzleMethod("initWithIcon:message:parentWindow:duration:", withMethod: "xmas_initWithIcon:message:parentWindow:duration:") } catch { Swift.print("Swizzling failed") }}

Anfänglich wollte ich alles in Swift machen. Aber es ist schwierig, die Swizzle-Init-Methode in Swift zu verwenden, also ist der schnellste Weg, das in Objective C zu tun. Dann durchlaufen wir einfach die Ansichtshierarchie, um das NSVisualEffectView innerhalb von NSPanel zu finden, um das Bild zu aktualisieren.

Interaktion mit DVTSourceTextView in XcodeColorSense

Ich arbeite meist mit Hex-Farben und ich möchte eine schnelle Möglichkeit, die Farbe zu sehen. Also habe ich XcodeColorSense entwickelt – es unterstützt Hex-Farben, RGBA und benannte Farben.

Die Idee ist einfach. Analysieren Sie die Zeichenfolge, um zu sehen, ob der Benutzer etwas mit UIColor tippt, und zeigen Sie eine kleine Overlay-Ansicht mit dieser Farbe als Hintergrund. Die Textansicht, die Xcode verwendet, ist vom Typ DVTSourceTextView im DVTKit Framework. Wir müssen auch auf NSTextViewDidChangeSelectionNotification hören, das immer dann ausgelöst wird, wenn ein NSTextView-Inhalt geändert wird.

func listenNotification() { NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(handleSelectionChange(_:)), name: NSTextViewDidChangeSelectionNotification, object: nil)}
func handleSelectionChange(note: NSNotification) { guard let DVTSourceTextView = NSClassFromString("DVTSourceTextView") as? NSObject.Type, object = note.object where object.isKindOfClass(DVTSourceTextView.self), let textView = object as? NSTextView else { return }

self.textView = textView}

Ich habe eine Matcher-Architektur, damit wir verschiedene Arten von UIColor-Konstruktionen erkennen können – zum Beispiel HexMatcher .

public struct HexMatcher: Matcher {
func check(line: String, selectedText: String) -> (color: NSColor, range: NSRange)? { let pattern1 = "\"#?{6}\"" let pattern2 = "0x{6}"
let ranges = .flatMap { return Regex.check(line, pattern: ) }
guard let range = ranges.first else { return nil }
let text = (line as NSString).substringWithRange(range).replace("0x", with: "").replace("\"", with: "") let color = NSColor.hex(text)
return (color: color, range: range) }}

Um das Overlay zu rendern, verwenden wir NSColorWell, das sich gut für die Anzeige einer Ansicht mit Hintergrund eignet. Die Position wird durch den Aufruf von firstRectForCharacterRange und einigen Punktkonvertierungen mit convertRectFromScreen und convertRect bestimmt.

Nutzung von NSTask und IDEWorkspaceWindowController in XcodeWay

Zu guter Letzt, mein geliebtes XcodeWay.

Ich habe festgestellt, dass ich von Xcode aus mit dem Kontext des aktuellen Projekts an verschiedene Orte gehen muss. Also habe ich XcodeWay als Plugin gebaut, das viele praktische Menüoptionen unter Window hinzufügt.

Da das Plugin im selben Xcode-Prozess läuft, hat es Zugriff auf das Hauptmenü NSApp.mainMenu?.itemWithTitle("Window") . Dort können wir das Menü ändern. XcodeWay ist so konzipiert, dass die Funktionalitäten über das Navigator-Protokoll einfach erweitert werden können.

@objc protocol Navigator: NSObjectProtocol { func navigate() var title: String { get }}

Für Ordner mit einem statischen Pfad wie Provisioning Profile ~/Library/MobileDevice/Provisioning Profiles oder User data Developer/Xcode/UserData können wir einfach den URL konstruieren und NSWorkspace.sharedWorkspace().openURL aufrufen. Bei dynamischen Ordnern, die je nach aktuellem Projekt variieren, muss mehr Arbeit geleistet werden.

Wie öffnen wir den Ordner für das aktuelle Projekt im Finder? Die Informationen über den aktuellen Projektpfad werden in IDEWorkspaceWindowController gespeichert. Dies ist eine Klasse, die Arbeitsbereichsfenster in Xcode verwaltet. Schauen Sie sich EnvironmentManager an, wo wir objc_getClass verwenden, um die Klassendefinition aus einer Zeichenkette abzurufen.

self.IDEWorkspaceWindowControllerClass = objc_getClass("IDEWorkspaceWindowController");
NSArray *workspaceWindowControllers = ;
id workSpace = nil;
for (id controller in workspaceWindowControllers) { if ( isEqual:]) { workSpace = ; }}

NSString * path = valueForKey:@"_pathString"];

Schließlich können wir valueForKey verwenden, um den Wert einer beliebigen Eigenschaft abzurufen, von der wir glauben, dass sie existiert. Auf diese Weise erhalten wir nicht nur den Projektpfad, sondern auch den Pfad zu der zu öffnenden Datei. So können wir activateFileViewerSelectingURLs auf NSWorkspace aufrufen, um Finder mit der ausgewählten Datei zu öffnen. Dies ist praktisch, da die Benutzer nicht nach dieser Datei im Finder suchen müssen.

Manchmal wollen wir einige Terminal-Befehle im aktuellen Projektordner ausführen. Um das zu erreichen, können wir NSTask mit dem Startfeld /usr/bin/open und den Argumenten verwenden. iTerm, wenn es wahrscheinlich konfiguriert ist, wird dies in einem neuen Tab öffnen.

Die Dokumente für iOS 7-Apps werden an einem festen Ort iPhone Simulator innerhalb der Anwendungsunterstützung abgelegt. Aber ab iOS 8 hat jede App eine eindeutige UUID und ihre Dokumentenordner sind schwer vorhersehbar.

~/Library/Developer/CoreSimulator/Devices/1A2FF360-B0A6-8127-95F3-68A6AB0BCC78/data/Container/Data/Application/

Wir können eine Karte erstellen und eine Nachverfolgung durchführen, um die generierte ID für das aktuelle Projekt zu finden, oder die Plist innerhalb jedes Ordners überprüfen, um den Bundle Identifier zu vergleichen.

Die schnelle Lösung, die mir eingefallen ist, war die Suche nach dem zuletzt aktualisierten Ordner. Jedes Mal, wenn wir das Projekt erstellen oder Änderungen innerhalb der Anwendung vornehmen, wird der Dokumentenordner aktualisiert. Hier können wir NSFileModificationDate verwenden, um den Ordner für das aktuelle Projekt zu finden.

Es gibt viele Hacks bei der Arbeit mit Xcode-Plugins, aber die Ergebnisse sind lohnend. Jede Minute, die wir am Tag einsparen, spart am Ende eine Menge Zeit.

Sicherheit und Freiheit

Mit großer Macht kommt große Verantwortung. Die Tatsache, dass Plugins tun können, was sie wollen, ist ein Alarmsignal für die Sicherheit. Ende 2015 gab es einen Malware-Angriff durch die Verbreitung einer modifizierten Version von Xcode, genannt XcodeGhost, die bösartigen Code in alle mit Xcode Ghost erstellten Apps einschleust. Es wird vermutet, dass die Malware unter anderem den Plugin-Mechanismus nutzt.

Wie die iOS-Apps, die wir aus dem Appstore herunterladen, werden auch macOS-Apps wie Xcode von Apple signiert, wenn wir sie aus dem Mac Appstore oder über offizielle Apple-Download-Links herunterladen.

Das Signieren des Codes Ihrer App garantiert den Nutzern, dass sie aus einer bekannten Quelle stammt und seit der letzten Signierung nicht verändert wurde. Bevor Ihre App App-Dienste integrieren, auf einem Gerät installiert oder im App Store eingereicht werden kann, muss sie mit einem von Apple ausgestellten Zertifikat signiert werden

Um potenzielle Malware wie diese zu vermeiden, hat Apple auf der WWDC 2016 die Xcode Source Editor Extension als einzige Möglichkeit angekündigt, Erweiterungen von Drittanbietern in Xcode zu laden. Das bedeutet, dass ab Xcode 8 keine Plugins mehr geladen werden können.

Source Editor Extension

Erweiterungen sind der empfohlene Ansatz, um Funktionalitäten auf sichere und eingeschränkte Weise hinzuzufügen.

App-Erweiterungen geben Nutzern Zugang zu den Funktionen und Inhalten deiner App in iOS und macOS. Zum Beispiel kann Ihre App jetzt als Widget auf dem Heute-Bildschirm erscheinen, neue Schaltflächen im Aktionsblatt hinzufügen, Fotofilter in der Fotos-App anbieten oder eine neue systemweite benutzerdefinierte Tastatur anzeigen.

Im Moment ist die einzige Erweiterung für Xcode der Quellcode-Editor, der es uns ermöglicht, den Inhalt einer Quelldatei zu lesen und zu ändern sowie die aktuelle Textauswahl im Editor zu lesen und zu ändern.

Die Erweiterung ist ein neues Ziel und läuft in einem anderen Prozess als Xcode. Das ist insofern gut, als dass sie Xcode in keiner Weise verändern kann, außer dass sie XCSourceEditorCommand entspricht, um den Inhalt des aktuellen Dokuments zu ändern.

protocol XCSourceEditorCommand {
 func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -&gt; Void)}

Xcode 8 hat viele Verbesserungen wie die neuen Code-Vervollständigungsfunktionen, Swift-Bild- und Farbliterale und Snippets. Dies führte dazu, dass viele Xcode-Plugins veraltet sind. Für einige unverzichtbare Plugins wie XVim ist dies für manche Benutzer unerträglich. Einige alte Plugin-Funktionen können mit dem aktuellen Source-Editor-Erweiterungssystem nicht erreicht werden.

Es sei denn, Sie kündigen Xcode

Ein Workaround, um die Einschränkung von Xcode 8 für Plugins zu umgehen, ist das Ersetzen der bestehenden Xcode-Signatur durch eine Technik namens resign. Das Resignieren ist sehr einfach – wir müssen nur ein selbstsigniertes Zertifikat erstellen und den Befehl codesign aufrufen. Danach sollte Xcode in der Lage sein, Plugins zu laden.

codesign -f -s MySelfSignedCertificate /Applications/Xcode.app

Es ist jedoch nicht möglich, mit resigniertem Xcode erstellte Apps einzureichen, da die Signatur nicht mit der offiziellen Version von Xcode übereinstimmt. Eine Möglichkeit besteht darin, zwei Xcodes zu verwenden: ein offizielles für die Verteilung und ein resigniertes für die Entwicklung.

Umstellung auf Xcode-Erweiterung

Die Xcode-Erweiterung ist der richtige Weg, also habe ich angefangen, meine Plugins in die Erweiterung zu verschieben. Da Xmas die Ansichtshierarchie ändert, kann es keine Erweiterung werden.

Farbliteral in XcodeColorSense2

Für den Farbsinn habe ich die Erweiterung von Grund auf neu geschrieben und sie XcodeColorSense2 genannt. Diese kann natürlich kein Overlay über der aktuellen Editoransicht anzeigen. Deshalb habe ich mich entschieden, das neue Color literal in Xcode 8+ zu verwenden.

Die Farbe wird in einem kleinen Kasten angezeigt. Es kann schwierig sein, ähnliche Farben zu unterscheiden, deshalb gebe ich auch den Namen an. Im Code geht es einfach darum, selections zu inspizieren und zu analysieren, um die Farbdeklaration zu finden.

func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void { guard let selection = invocation.buffer.selections.firstObject as? XCSourceTextRange else { completionHandler(nil) return }
let lineNumber = selection.start.line
guard lineNumber < invocation.buffer.lines.count, let line = invocation.buffer.lines as? String else { completionHandler(nil) return }
guard let hex = findHex(string: line) else { completionHandler(nil) return }
let newLine = process(line: line, hex: hex)
invocation.buffer.lines.replaceObject(at: lineNumber, with: newLine)
completionHandler(nil) }}

Der Großteil der Funktionalität ist in mein Framework Farge eingebettet, aber ich kann keine Möglichkeit finden, das Framework innerhalb der Xcode-Erweiterung zu verwenden.

Da die Erweiterungsfunktion nur über das Editor-Menü zugänglich ist, können wir eine Tastenbindung zum Aufrufen dieses Menüpunkts anpassen. Ich wähle zum Beispiel Cmd+Ctrl+S, um Farbinformationen ein- und auszublenden.

Das ist natürlich nicht intuitiv im Vergleich zum ursprünglichen Plugin, aber besser als nichts.

Wie man Xcode-Erweiterungen debuggt

Das Arbeiten und Debuggen von Erweiterungen ist einfach. Wir können Xcode verwenden, um Xcode zu debuggen. Die debuggte Version von Xcode hat ein graues Symbol.

Wie man Xcode-Erweiterungen installiert

Die Erweiterung muss eine zugehörige macOS-App haben. Diese kann über den Mac Appstore vertrieben werden oder selbst signiert sein. Wie das geht, habe ich in einem Artikel beschrieben.

Alle Erweiterungen für eine App müssen explizit über die „Systemeinstellungen“ aktiviert werden.

Die Xcode-Erweiterung funktioniert vorerst nur mit Editor, wir müssen also eine Quelldatei öffnen, damit das Editor-Menü Wirkung zeigt.

AppleScript in XcodeWay

In Xcode-Erweiterungen funktionieren NSWorkspace, NSTask und private class construction nicht mehr. Da ich die Finder Sync-Erweiterung in FinderGo verwendet habe, dachte ich, ich könnte das gleiche AppleScript-Skript für die Xcode-Erweiterung ausprobieren.

AppleScript ist eine von Apple entwickelte Skriptsprache. Sie ermöglicht es Benutzern, skriptfähige Macintosh-Programme sowie Teile von macOS selbst direkt zu steuern. Sie können Skripte – Sätze von geschriebenen Anweisungen – erstellen, um sich wiederholende Aufgaben zu automatisieren, Funktionen aus mehreren skriptfähigen Programmen zu kombinieren und komplexe Arbeitsabläufe zu erstellen.

Um AppleScript auszuprobieren, können Sie die in macOS integrierte App Script Editor verwenden, um Prototyp-Funktionen zu schreiben. Die Funktionsdeklaration beginnt mit on und endet mit end. Um mögliche Konflikte mit Systemfunktionen zu vermeiden, verwende ich normalerweise my als Präfix. So verwende ich System Events, um das Home-Verzeichnis zu erhalten.

Die Terminologie für Skripte der Benutzeroberfläche findet sich in der „Processes Suite“ des Skriptwörterbuchs „System Events“. Diese Suite enthält Terminologie für die Interaktion mit den meisten Arten von Benutzeroberflächenelementen, einschließlich:

  • Fenster
  • Schaltflächen
  • Kontrollkästchen
  • Menüs
  • Radiotasten
  • Textfelder.

In System Events repräsentiert die process Klasse eine laufende App.

Viele gute Bürger-Apps unterstützen AppleScript, indem sie einige ihrer Funktionalitäten offenlegen, so dass diese von anderen Apps genutzt werden können. Hier ist, wie ich den aktuellen Song von Spotify in Lyrics bekomme.

tell application "Spotify" set trackId to id of current track as string set trackName to name of current track as string set artworkUrl to artwork url of current track as string set artistName to artist of current track as string set albumName to album of current track as string return trackId & "---" & trackName & "---" & artworkUrl & "---" & artistName & "---" & albumNameend tell

Um alle möglichen Befehle einer bestimmten App zu erhalten, können wir das Wörterbuch im Skript-Editor öffnen. Dort können wir erfahren, welche Funktionen und Parameter unterstützt werden.

Wenn Sie denken, dass Objective C schwer ist, ist AppleScript noch viel schwerer. Die Syntax ist langatmig und fehleranfällig. Zu Ihrer Information finden Sie hier die gesamte Skriptdatei, die XcodeWay antreibt.

Um einen bestimmten Ordner zu öffnen, sagen Sie Finder mit POSIX file. Ich refaktorisiere jede Funktionalität in eine Funktion, um den Code besser wiederverwenden zu können.

on myOpenFolder(myPath)tell application "Finder"activateopen myPath as POSIX fileend tellend myOpenFolder

Um AppleScript in einer macOS-App oder -Erweiterung auszuführen, müssen wir einen AppleScript-Deskriptor mit der korrekten Prozess-Seriennummer und den Ereigniskennungen erstellen.

func eventDescriptior(functionName: String) -> NSAppleEventDescriptor { var psn = ProcessSerialNumber(highLongOfPSN: 0, lowLongOfPSN: UInt32(kCurrentProcess)) let target = NSAppleEventDescriptor( descriptorType: typeProcessSerialNumber, bytes: &psn, length: MemoryLayout<ProcessSerialNumber>.size )
let event = NSAppleEventDescriptor( eventClass: UInt32(kASAppleScriptSuite), eventID: UInt32(kASSubroutineEvent), targetDescriptor: target, returnID: Int16(kAutoGenerateReturnID), transactionID: Int32(kAnyTransactionID) )
let function = NSAppleEventDescriptor(string: functionName) event.setParam(function, forKeyword: AEKeyword(keyASSubroutineName))
return event}

Andere Aufgaben, wie das Überprüfen des aktuellen Git-Remotes, sind etwas schwieriger. Oft möchte ich den Link der Datei, die ich gerade debugge, an meinen entfernten Teamkollegen weitergeben, damit er weiß, auf welche Datei ich mich beziehe. Dies ist möglich durch die Verwendung von shell script innerhalb von AppleScript .

on myGitHubURL()set myPath to myProjectPath()set myConsoleOutput to (do shell script "cd " & quoted form of myPath & "; git remote -v")set myRemote to myGetRemote(myConsoleOutput)set myUrl to (do shell script "cd " & quoted form of myPath & "; git config --get remote." & quoted form of myRemote & ".url")set myUrlWithOutDotGit to myRemoveSubString(myUrl, ".git")end myGitHubURL

Wir können quoted und String-Verkettung verwenden, um Strings zu bilden. Glücklicherweise können wir das Foundation-Framework und bestimmte Klassen offenlegen. Hier ist, wie ich NSString aussetze, um die Vorteile aller vorhandenen Funktionen zu nutzen. Das Schreiben von String-Manipulationen von Grund auf mit einfachem AppleScript wird viel Zeit in Anspruch nehmen.

use scripting additionsuse framework "Foundation"property NSString : a reference to current application's NSString

Damit können wir unsere anderen Funktionen für die String-Bearbeitung aufbauen.

on myRemoveLastPath(myPath)set myString to NSString's stringWithString:myPathset removedLastPathString to myString's stringByDeletingLastPathComponentremovedLastPathString as textend myRemoveLastPath

Eine coole Funktion, die XcodeWay unterstützt, ist die Möglichkeit, das Dokumentverzeichnis für die aktuelle App im Simulator zu wechseln. Dies ist praktisch, wenn wir ein Dokument untersuchen müssen, um gespeicherte oder zwischengespeicherte Daten zu überprüfen. Da das Verzeichnis dynamisch ist, ist es schwer zu erkennen. Wir können jedoch das Verzeichnis nach der letzten Aktualisierung sortieren. Im Folgenden sehen Sie, wie wir mehrere shell scripts-Befehle verketten, um den Ordner zu finden.

on myOpenDocument()set command1 to "cd ~/Library/Developer/CoreSimulator/Devices/;"set command2 to "cd `ls -t | head -n 1`/data/Containers/Data/Application;"set command3 to "cd `ls -t | head -n 1`/Documents;"set command4 to "open ."do shell script command1 & command2 & command3 & command4end myOpenDocument

Diese Funktion hat mir bei der Entwicklung von Gallery sehr geholfen, um zu prüfen, ob Videos und heruntergeladene Bilder am richtigen Ort gespeichert sind.

Allerdings scheint keines der Skripts zu funktionieren. Skripte waren schon immer Teil von macOS, seit 1993. Aber mit dem Aufkommen des Mac Appstore und Sicherheitsbedenken wurde AppleScript schließlich Mitte 2012 eingeschränkt. Damals wurde die App Sandbox eingeführt.

App Sandbox

App Sandbox ist eine Technologie zur Zugriffskontrolle in macOS, die auf der Kernel-Ebene durchgesetzt wird. Sie soll den Schaden für das System und die Daten des Benutzers begrenzen, wenn eine App kompromittiert wird. Apps, die über den Mac App Store vertrieben werden, müssen App Sandbox unterstützen.

Damit eine Xcode-Erweiterung von Xcode geladen werden kann, muss sie auch App Sandbox unterstützen.

Zu Beginn der Durchsetzung von App Sandbox konnten wir die App Sandbox Temporary Exception verwenden, um unserer App vorübergehend Zugriff auf Apple Script zu gewähren.

Das ist jetzt nicht mehr möglich.

Ein AppleScript kann nur ausgeführt werden, wenn es sich im Ordner ~/Library/Application Scripts befindet.

Wie man benutzerdefinierte Skripte installiert

MacOS-Apps oder -Erweiterungen können nicht einfach von sich aus Skripte in den Application Scripts installieren. Sie brauchen die Zustimmung des Benutzers.

Eine Möglichkeit ist, Read/Write zu aktivieren und einen Dialog mit NSOpenPanel anzuzeigen, um den Benutzer aufzufordern, den Ordner für die Installation unserer Skripte auszuwählen.

Für XcodeWay habe ich mich entschieden, ein Installations-Shell-Skript bereitzustellen, damit der Benutzer eine schnelle Möglichkeit hat, Skripte zu installieren.

#!/bin/bash
set -euo pipefail
DOWNLOAD_URL=https://raw.githubusercontent.com/onmyway133/XcodeWay/master/XcodeWayExtensions/Script/XcodeWayScript.scptSCRIPT_DIR="${HOME}/Library/Application Scripts/com.fantageek.XcodeWayApp.XcodeWayExtensions"
mkdir -p "${SCRIPT_DIR}"curl $DOWNLOAD_URL -o "${SCRIPT_DIR}/XcodeWayScript.scpt"

AppleScript ist sehr mächtig. All dies wird explizit gemacht, so dass der Benutzer die vollständige Kontrolle darüber hat, welche Dinge getan werden können.

Wie eine Erweiterung wird ein Skript asynchron in einem anderen Prozess ausgeführt, der XPC für die Kommunikation zwischen den Prozessen verwendet. Dies erhöht die Sicherheit, da ein Skript keinen Zugriff auf den Adressraum unserer App oder Erweiterung hat.

Mehr Sicherheit in macOS Mojave

Dieses Jahr, auf der WWDC 2018, hat Apple macOS Mojave vorgestellt, das sich auf viele Sicherheitsverbesserungen konzentriert. In der Rubrik Your Apps and the Future of macOS Security erfahren wir mehr über neue Sicherheitsanforderungen für macOS-Apps. Eine davon ist die Nutzungsbeschreibung für AppleEvents.

unable to load info.plist exceptions (egpu overrides)

Wir haben früher die Nutzungsbeschreibung für viele Berechtigungen in iOS deklariert, wie z.B. Fotobibliothek, Kamera und Push-Benachrichtigungen. Jetzt müssen wir die Verwendungsbeschreibung für AppleEvents deklarieren.

Quelle: https://www.felix-schwarz.org/blog/2018/08/new-apple-event-apis-in-macos-mojave

Wenn unsere Erweiterung zum ersten Mal versucht, einige AppleScript-Befehle auszuführen, wird der obige Dialog angezeigt, um die Zustimmung des Benutzers einzuholen. Der Benutzer kann die Erlaubnis erteilen oder verweigern, aber für Xcode sagen Sie bitte ja?

Die Lösung für uns ist, NSAppleEventsUsageDescription in unserem App-Target zu deklarieren. Wir müssen nur im App-Target deklarieren, nicht im Extension-Target.

<key>NSAppleEventsUsageDescription</key><string>Use AppleScript to open folders</string>

Wie geht es jetzt weiter

Huff huff, puh! Danke, dass Sie diese lange Reise mitgemacht haben. Die Entwicklung von Frameworks und Tools nimmt viel Zeit in Anspruch, vor allem Plugins und Erweiterungen – wir müssen sie ständig ändern, um sie an neue Betriebssysteme und Sicherheitsanforderungen anzupassen. Aber es ist ein lohnender Prozess, da wir mehr gelernt haben und einige Werkzeuge haben, die unsere kostbare Zeit sparen.

Für Ihre Referenz, hier sind meine Erweiterungen, die vollständig Open Source sind.

  • XcodeWay
  • XcodeColorSense2