Wie Sie Ihre Xcode-Plugins in Xcode-Erweiterungen umwandeln
von Khoa Pham
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.
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 DVTPluginCompatibilityUUIDs
aktualisieren 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?) -> 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.
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