Come convertire i tuoi plugin Xcode in estensioni Xcode
di Khoa Pham
Xcode è un IDE indispensabile per gli sviluppatori iOS e macOS. Fin dai primi giorni, la possibilità di costruire e installare plugin personalizzati ci ha dato un enorme impulso alla produttività. Non è passato molto tempo prima che Apple introducesse l’estensione Xcode a causa delle preoccupazioni sulla privacy.
Ho costruito alcuni plugin ed estensioni Xcode come XcodeWay, XcodeColorSense, XcodeColorSense2 e Xmas. È stata un’esperienza gratificante. Ho imparato molto e la produttività che ho guadagnato è stata considerevole. In questo post spiego come ho convertito i miei plugin Xcode in estensioni, e l’esperienza che ho avuto nel farlo.
Il mio primo plugin Xcode: XcodeWay
Ho scelto una persona pigra per fare un lavoro difficile. Perché una persona pigra troverà un modo facile per farlo
Mi piace molto la suddetta citazione di Bill Gates. Cerco di evitare compiti ripetitivi e noiosi. Ogni volta che mi ritrovo a rifare gli stessi compiti, scrivo script e strumenti per automatizzarli. Fare questo richiede un po’ di tempo, ma sarò un po’ più pigro nel prossimo futuro.
Oltre all’interesse per la costruzione di framework e strumenti open source, mi piace estendere l’IDE che sto usando – soprattutto Xcode.
Ho iniziato a sviluppare iOS nel 2014. Volevo un modo rapido per navigare in molti luoghi direttamente da Xcode con il contesto del progetto corrente. Ci sono molte volte che vogliamo:
- aprire la cartella del progetto corrente nel “Finder” per cambiare alcuni file
- aprire il terminale per eseguire alcuni comandi
- aprire il file corrente in GitHub per dare rapidamente il link a un compagno di lavoro
- o per aprire altre cartelle come temi, plugin, frammenti di codice, log del dispositivo.
Ogni piccola parte di tempo che risparmiamo ogni giorno conta.
Ho pensato che sarebbe una bella idea scrivere un plugin per Xcode che ci permetta di fare tutte queste cose proprio dentro Xcode. Invece di aspettare che altre persone lo facciano, mi sono tirato su le maniche e ho scritto il mio primo plugin per Xcode – XcodeWay- e l’ho condiviso come open source.
Cosa sono i plugin di Xcode?
I plugin di Xcode non sono ufficialmente supportati da Xcode o raccomandati da Apple. Non ci sono documenti su di loro. I posti migliori dove possiamo impararli sono attraverso il codice sorgente dei plugin esistenti e qualche tutorial.
Un plugin Xcode è solo un bundle di tipo xcplugin
e si trova a ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins
. Xcode, all’avvio, caricherà qualsiasi plugin Xcode presente in questa cartella. I plugin sono eseguiti nello stesso processo di Xcode, quindi potrebbero fare qualsiasi cosa come Xcode. Un bug in qualsiasi plugin può causare il crash di Xcode.
Per fare un plugin Xcode, crea un macOS Bundle
con una classe che estende da NSObject
, e ha un inizializzatore che accetta NSBundle
, per esempio in Xcode:
class Xmas: NSObject {
var bundle: NSBundle
init(bundle: NSBundle) { self.bundle = bundle super.init() }}
Inside Info.plist
, dobbiamo:
- dichiarare questa classe come la classe principale di ingresso per il plugin, e
- che questo bundle non ha UI, perché creiamo controlli UI e li aggiungiamo all’interfaccia Xcode durante il runtime
<key>NSPrincipalClass</key><string>Xmas</string><key>XCPluginHasUI</key><false/>
Un altro problema con i plugin Xcode è che dobbiamo continuamente aggiornare DVTPluginCompatibilityUUIDs
. Questo cambia ogni volta che esce una nuova versione di Xcode. Senza l’aggiornamento, Xcode si rifiuta di caricare il plugin.
Cosa possono fare i plugin di Xcode
Molti sviluppatori costruiscono plugin di Xcode perché gli mancano caratteristiche specifiche che si trovano in altri IDE come Sublime Text, AppCode o Atom.
Siccome i plugin di Xcode sono caricati nello stesso processo di Xcode, possono fare tutto quello che può fare Xcode. L’unico limite è la nostra immaginazione. Possiamo sfruttare Objective C Runtime per scoprire framework e funzioni private. Poi LLDB e Symbolic breakpoint possono essere usati ulteriormente per ispezionare il codice in esecuzione e modificare i loro comportamenti. Possiamo anche usare lo swizzling per cambiare l’implementazione di qualsiasi codice in esecuzione. Scrivere plugin per Xcode è difficile – un sacco di congetture, e a volte è richiesta una buona conoscenza dell’assemblaggio.
Nel periodo d’oro dei plugin, c’era un popolare gestore di plugin, che era esso stesso un plugin, chiamato Alcatraz. Poteva installare altri plugin, che fondamentalmente scaricava solo il file xcplugin
e lo spostava nella cartella Plug Ins
.
Per avere un’idea di cosa possono fare i plugin, diamo un’occhiata ad alcuni plugin popolari.
Xvim
Il primo della lista è Xvim, che aggiunge i keybindings di Vim proprio dentro Xcode. Supporta quasi tutte le associazioni di tasti che avevamo in Terminal.
SCXcodeMiniMap
Se ti manca la modalità MiniMap in Sublime Text, puoi usare SCXcodeMiniMap per aggiungere un pannello con la mappa destra nell’editor Xcode.
FuzzyAutocompletePlugin
Prima della versione 9, Xcode non aveva un vero completamento automatico – era solo basato sul prefisso. È qui che FuzzyAutocompletePlugin ha brillato. Esegue il completamento automatico fuzzy basato sulla caratteristica nascosta IDEOpenQuicklyPattern
in Xcode.
KSImageNamed-Xcode
Per visualizzare un’immagine del bundle dentro UIImageView
, spesso usiamo il metodo imageNamed
. Ma ricordare esattamente il nome del file immagine è difficile. KSImageNamed-Xcode è qui per aiutare. Otterrete una lista di nomi di immagini suggeriti automaticamente quando inizierete a digitare.
ColorSense-for-Xcode
Un altro prurito durante lo sviluppo è quello di lavorare con UIColor
, che usa lo spazio colore RGBA. Non abbiamo un indicatore visivo del colore che specifichiamo, ed eseguire manualmente il controllo può richiedere molto tempo. Fortunatamente c’è ColorSense-for-Xcode che mostra il colore usato e il pannello di selezione dei colori per selezionare facilmente il colore giusto.
LinkedConsole
In AppCode, possiamo saltare a una linea specifica nel file che viene registrata all’interno della console. Se vi manca questa caratteristica in Xcode, potete usare LinkedConsole. Questo abilita i link cliccabili all’interno della console di Xcode in modo da poter saltare istantaneamente a quel file.
Il duro lavoro dietro i plugin Xcode
Fare un plugin Xcode non è facile. Non solo dobbiamo conoscere la programmazione di macOS, ma dobbiamo anche immergerci in profondità nella gerarchia delle viste di Xcode. Dobbiamo esplorare i framework privati e le API per iniettare la funzione che vogliamo.
Ci sono pochissimi tutorial su come fare i plugin ma, fortunatamente, la maggior parte dei plugin sono open source così possiamo capire come funzionano. Dato che ho fatto alcuni plugin, posso dare alcuni dettagli tecnici su di essi.
I plugin Xcode sono fatti di solito con due framework privati: DVTKit
e IDEKit
. I framework di sistema sono a /System/Library/PrivateFrameworks
ma i framework che Xcode usa esclusivamente sono sotto /Applications/Xcode.app/Contents/
, lì puoi trovare Frameworks
, OtherFrameworks
e SharedFrameworks
.
C’è uno strumento class-dump che può generare gli header dal bundle dell’app Xcode. Con i nomi delle classi e i metodi, puoi chiamare NSClassFromString
per ottenere la classe dal nome.
Swizzling DVTBezelAlertPanel framework in Xmas
Il Natale mi ha sempre dato una sensazione speciale, così ho deciso di fare Xmas, che mostra un’immagine natalizia casuale invece della vista di allarme predefinita. La classe usata per rendere questa vista è DVTBezelAlertPanel nel framework DVTKit. Il mio articolo sulla costruzione del plugin è qui.
Con Objective C Runtime, c’è una tecnica chiamata swizzling, che può cambiare l’implementazione e la firma del metodo di qualsiasi classe e metodo in esecuzione.
Qui, per cambiare il contenuto di quella vista di avviso, dobbiamo scambiare l’inizializzatore initWithIcon:message:parentWindow:duration:
con il nostro metodo. Lo facciamo prima ascoltando NSApplicationDidFinishLaunchingNotification
che viene notificato quando un plugin di macOS, in questo caso Xcode, viene lanciato.
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") }}
Inizialmente mi piaceva fare tutto in Swift. Ma è difficile usare il metodo swizzle init in Swift, quindi il modo più veloce è farlo in Objective C. Poi semplicemente attraversiamo la gerarchia della vista per trovare il NSVisualEffectView
dentro NSPanel
per aggiornare l’immagine.
Interagire con DVTSourceTextView in XcodeColorSense
Lavoro soprattutto con i colori esadecimali e voglio un modo veloce per vedere il colore. Così ho costruito XcodeColorSense – supporta i colori esadecimali, RGBA e i colori nominati.
L’idea è semplice. Analizza la stringa per vedere se l’utente sta digitando qualcosa relativo a UIColor
, e mostra una piccola vista in sovraimpressione con quel colore come sfondo. La vista di testo che Xcode usa è di tipo DVTSourceTextView
nel framework DVTKit
. Abbiamo anche bisogno di ascoltare NSTextViewDidChangeSelectionNotification
che viene attivato ogni volta che un qualsiasi contenuto NSTextView
viene cambiato.
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}
Ho avuto un’architettura Matcher in modo da poter rilevare diversi tipi di costruzioni UIColor
– per esempio 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) }}
Per rendere la sovrapposizione, usiamo NSColorWell
che è buono per mostrare una vista con sfondo. La posizione è determinata chiamando firstRectForCharacterRange
e alcune conversioni di punti con convertRectFromScreen
e convertRect
.
Using NSTask and IDEWorkspaceWindowController in XcodeWay
Finalmente, il mio amato XcodeWay.
Mi sono trovato a dover andare in posti diversi da Xcode con il contesto del progetto corrente. Così ho costruito XcodeWay come un plugin che aggiunge un sacco di pratiche opzioni di menu sotto Window
.
Siccome il plugin viene eseguito nello stesso processo Xcode, ha accesso al menu principale NSApp.mainMenu?.itemWithTitle("Window")
. Lì possiamo modificare il menu. XcodeWay è progettato per estendere facilmente le funzionalità attraverso il suo protocollo Navigator.
@objc protocol Navigator: NSObjectProtocol { func navigate() var title: String { get }}
Per le cartelle con un percorso statico come Provisioning Profile ~/Library/MobileDevice/Provisioning Profiles
o User data Developer/Xcode/UserData
, possiamo semplicemente costruire il URL
e chiamare NSWorkspace.sharedWorkspace().openURL
. Per le cartelle dinamiche che variano a seconda del progetto corrente, bisogna fare più lavoro.
Come facciamo ad aprire la cartella del progetto corrente nel Finder? Le informazioni per il percorso del progetto corrente sono tenute dentro IDEWorkspaceWindowController
. Questa è una classe che gestisce le finestre dello spazio di lavoro in Xcode. Date un’occhiata a EnvironmentManager dove usiamo objc_getClass per ottenere la definizione della classe da una stringa.
self.IDEWorkspaceWindowControllerClass = objc_getClass("IDEWorkspaceWindowController");
NSArray *workspaceWindowControllers = ;
id workSpace = nil;
for (id controller in workspaceWindowControllers) { if ( isEqual:]) { workSpace = ; }}
NSString * path = valueForKey:@"_pathString"];
Infine, possiamo utilizzare valueForKey
per ottenere il valore di qualsiasi proprietà che pensiamo esista. In questo modo non solo otteniamo il percorso del progetto, ma anche il percorso del file di apertura. Così possiamo chiamare activateFileViewerSelectingURLs
su NSWorkspace
per aprire il Finder con quel file selezionato. Questo è comodo perché gli utenti non hanno bisogno di cercare quel file nel Finder.
Molte volte vogliamo eseguire alcuni comandi del terminale sulla cartella corrente del progetto. Per ottenere ciò, possiamo usare NSTask
con launch pad /usr/bin/open
e argomenti . iTerm, se configurato probabilmente, aprirà questo in una nuova scheda.
I documenti per le app di iOS 7 sono collocati nella posizione fissa iPhone Simulator
dentro Application Support. Ma, da iOS 8, ogni app ha un UUID unico e le loro cartelle di documenti sono difficili da prevedere.
~/Library/Developer/CoreSimulator/Devices/1A2FF360-B0A6-8127-95F3-68A6AB0BCC78/data/Container/Data/Application/
Possiamo costruire una mappa ed eseguire il tracking per trovare l’ID generato per il progetto corrente, o controllare il plist all’interno di ogni cartella per confrontare l’identificatore del bundle.
La soluzione rapida che mi è venuta in mente è quella di cercare la cartella più recente aggiornata. Ogni volta che costruiamo il progetto, o facciamo delle modifiche all’interno dell’app, la loro cartella dei documenti viene aggiornata. È qui che possiamo fare uso di NSFileModificationDate
per trovare la cartella del progetto corrente.
Ci sono molti hack quando si lavora con i plugin Xcode, ma i risultati sono gratificanti. Ogni pochi minuti che risparmiamo ogni giorno finiscono per risparmiare complessivamente molto tempo.
Sicurezza e libertà
Con grande potere arriva una grande responsabilità. Il fatto che i plugin possano fare quello che vogliono fa suonare un allarme per la sicurezza. Alla fine del 2015, c’è stato un attacco malware distribuendo una versione modificata di Xcode, chiamata XcodeGhost, che inietta codice maligno in qualsiasi app costruita con Xcode Ghost. Si ritiene che il malware utilizzi, tra le altre cose, il meccanismo dei plugin.
Come le app iOS che scarichiamo dall’Appstore, le app macOS come Xcode sono firmate da Apple quando le scarichiamo dal Mac Appstore o attraverso i link di download ufficiali di Apple.
La firma del codice della tua app assicura agli utenti che proviene da una fonte nota e che l’app non è stata modificata dall’ultima volta che è stata firmata. Prima che la tua app possa integrare servizi di app, essere installata su un dispositivo o essere inviata all’App Store, deve essere firmata con un certificato rilasciato da Apple
Per evitare potenziali malware come questo, al WWDC 2016 Apple ha annunciato l’estensione Xcode Source Editor come unico modo per caricare estensioni di terze parti in Xcode. Questo significa che, da Xcode 8, i plugin non possono essere caricati.
Source Editor Extension
L’estensione è l’approccio consigliato per aggiungere in modo sicuro funzionalità in modi limitati.
Le estensioni delle app danno agli utenti l’accesso alle funzionalità e ai contenuti della tua app in tutto iOS e macOS. Per esempio, la tua app può ora apparire come un widget nella schermata Oggi, aggiungere nuovi pulsanti nel foglio Azioni, offrire filtri fotografici all’interno dell’app Foto, o mostrare una nuova tastiera personalizzata a livello di sistema.
Per ora, l’unica estensione di Xcode è Source Editor, che ci permette di leggere e modificare il contenuto di un file sorgente, così come leggere e modificare la selezione di testo corrente all’interno dell’editor.
L’estensione è un nuovo obiettivo e viene eseguito in un processo diverso da Xcode. Questo è un bene in quanto non può alterare Xcode in nessun modo se non conformandosi a XCSourceEditorCommand
per modificare il contenuto del documento corrente.
protocol XCSourceEditorCommand {
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void)}
Xcode 8 ha molti miglioramenti come le nuove funzionalità di completamento del codice, i letterali di immagine e colore Swift e gli snippet. Questo ha portato alla deprecazione di molti plugin di Xcode. Per alcuni plugin indispensabili come XVim, questo è insopportabile per alcune persone. Alcune vecchie caratteristiche dei plugin non possono essere ottenute con l’attuale sistema Source Editor Extension.
A meno che non si dimetta Xcode
Un workaround per aggirare la restrizione di Xcode 8 per i plugin, è sostituire la firma esistente di Xcode con una tecnica chiamata resign. Rassegnarsi è molto facile – abbiamo solo bisogno di creare un certificato autofirmato e chiamare il comando codesign
. Dopo questo, Xcode dovrebbe essere in grado di caricare i plugin.
codesign -f -s MySelfSignedCertificate /Applications/Xcode.app
Non è, tuttavia, possibile presentare applicazioni costruite con Xcode rassegnato poiché la firma non corrisponde alla versione ufficiale di Xcode. Un modo è quello di usare due Xcode: uno ufficiale per la distribuzione e uno dimesso per lo sviluppo.
Spostamento su Xcode extension
Xcode extension è la strada da seguire, così ho iniziato a spostare i miei plugin su extension. Per Xmas, dato che modifica la gerarchia delle viste, non può diventare un’estensione.
Colore letterale in XcodeColorSense2
Per il senso del colore, ho riscritto l’estensione da zero, e l’ho chiamata XcodeColorSense2. Questo, ovviamente, non può mostrare una sovrapposizione sulla vista corrente dell’editor. Così ho scelto di utilizzare il nuovo Color literal
che si trova in Xcode 8+.
Il colore è mostrato in una piccola scatola. Può essere difficile distinguere colori simili, ecco perché includo anche il nome. Il codice riguarda semplicemente l’ispezione di selections
e il parsing per trovare la dichiarazione del colore.
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) }}
La maggior parte della funzionalità è incorporata nel mio framework Farge, ma non riesco a trovare un modo per utilizzare il framework all’interno dell’estensione Xcode.
Siccome la funzione di estensione è accessibile solo attraverso il menu Editor, possiamo personalizzare un binding di tasti per richiamare questa voce di menu. Per esempio io scelgo Cmd+Ctrl+S
per mostrare e nascondere le informazioni sui colori.
Questo, naturalmente, non è intuitivo rispetto al plugin originale, ma è meglio di niente.
Come fare il debug delle estensioni Xcode
Il lavoro e il debug delle estensioni è semplice. Possiamo usare Xcode per fare il debug di Xcode. La versione debuggata di Xcode ha un’icona grigia.
Come installare le estensioni Xcode
L’estensione deve avere un’app macOS di accompagnamento. Questa può essere distribuita su Mac Appstore o autofirmata. Ho scritto un articolo su come farlo.
Tutte le estensioni per un’app devono essere esplicitamente abilitate attraverso “Preferenze di sistema”.
L’estensione Xcode funziona solo con l’editor per ora, quindi dobbiamo aprire un file sorgente perché il menu Editor
abbia effetto.
AppleScript in XcodeWay
Nelle estensioni Xcode, NSWorkspace
, NSTask
e la costruzione di classi private non funzionano più. Dato che ho usato Finder Sync Extension in FinderGo, ho pensato di provare lo stesso scripting AppleScript per l’estensione Xcode.
AppleScript è un linguaggio di scripting creato da Apple. Permette agli utenti di controllare direttamente le applicazioni Macintosh scrivibili, così come parti di macOS stesso. Puoi creare script – insiemi di istruzioni scritte – per automatizzare attività ripetitive, combinare funzioni di più applicazioni scrivibili e creare flussi di lavoro complessi.
Per provare AppleScript, puoi usare l’app Script Editor integrato in macOS per scrivere funzioni prototipo. La dichiarazione delle funzioni inizia con on
e finisce con end
. Per evitare potenziali conflitti con le funzioni di sistema, di solito uso my
come prefisso. Ecco come mi affido a System Events per ottenere la home directory.
La terminologia di scripting dell’interfaccia utente si trova nella “Suite dei processi” del dizionario di scripting “System Events”. Questa suite include la terminologia per interagire con la maggior parte dei tipi di elementi dell’interfaccia utente, inclusi:
- finestre
- pulsanti
- checkbox
- menu
- pulsanti radio
- campi di testo.
In System Events, la classe process
rappresenta un’applicazione in esecuzione.
Molte buone applicazioni cittadine supportano AppleScript esponendo alcune delle loro funzionalità, in modo che queste possano essere usate da altre applicazioni. Ecco come ottengo la canzone corrente da Spotify in Lyrics.
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
Per ottenere tutti i possibili comandi di una certa app, possiamo aprire il dizionario in Script Editor. Lì possiamo sapere quali funzioni e parametri sono supportati.
Se pensate che Objective C sia difficile, AppleScript è molto più difficile. La sintassi è verbosa e soggetta a errori. Per vostro riferimento, ecco l’intero file di script che alimenta XcodeWay.
Per aprire una certa cartella, dite a Finder
usando POSIX file
. Rifattorizzo ogni funzionalità in funzione per un migliore riutilizzo del codice.
on myOpenFolder(myPath)tell application "Finder"activateopen myPath as POSIX fileend tellend myOpenFolder
Poi, per eseguire AppleScript all’interno di un’app macOS o di un’estensione, dobbiamo costruire un descrittore AppleScript con il corretto numero di serie del processo e gli identificatori degli eventi.
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}
Altri compiti, come controllare il Git remoto corrente, sono un po’ più complicati. Molte volte voglio condividere il link del file che sto debuggando con il mio compagno di squadra remoto, in modo che sappia a quale file sto facendo riferimento. Questo è fattibile usando shell script
dentro 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
Possiamo usare quoted
e la concatenazione di stringhe per formare stringhe. Fortunatamente possiamo esporre il framework Foundation
e certe classi. Ecco come espongo NSString
per sfruttare tutte le funzionalità esistenti. Scrivere la manipolazione delle stringhe da zero usando AppleScript richiederà molto tempo.
use scripting additionsuse framework "Foundation"property NSString : a reference to current application's NSString
Con questo possiamo costruire le nostre altre funzioni per la gestione delle stringhe.
on myRemoveLastPath(myPath)set myString to NSString's stringWithString:myPathset removedLastPathString to myString's stringByDeletingLastPathComponentremovedLastPathString as textend myRemoveLastPath
Una caratteristica interessante che XcodeWay supporta è la capacità di andare nella directory dei documenti per l’applicazione corrente nel simulatore. Questo è comodo quando abbiamo bisogno di ispezionare un documento per controllare i dati salvati o nella cache. La directory è dinamica, quindi è difficile da individuare. Possiamo, tuttavia, ordinare la directory per il più recente aggiornamento. Qui sotto c’è come concateniamo più comandi shell scripts
per trovare la cartella.
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
Questa funzione mi ha aiutato molto quando sviluppavo Gallery per controllare se i video e le immagini scaricate sono salvati nel posto giusto.
Tuttavia, nessuno degli script sembra funzionare. Lo scripting ha sempre fatto parte di macOS dal 1993. Ma, con l’avvento del Mac Appstore e i problemi di sicurezza, AppleScript è stato finalmente limitato a metà del 2012. Questo è stato quando App Sandbox è stato applicato.
App Sandbox
App Sandbox è una tecnologia di controllo degli accessi fornita in macOS, applicata a livello del kernel. È progettata per contenere i danni al sistema e ai dati dell’utente se un’app viene compromessa. Le app distribuite attraverso il Mac App Store devono adottare App Sandbox.
Perché un’estensione Xcode sia caricata da Xcode, deve supportare anche App Sandbox.
All’inizio dell’applicazione di App Sandbox, potremmo usare App Sandbox Temporary Exception per concedere temporaneamente alla nostra app l’accesso a Apple Script.
Questo ora non è possibile.
L’unico modo in cui AppleScript può essere eseguito è se risiede all’interno della cartella ~/Library/Application Scripts
.
Come installare script personalizzati
Le app o estensioni macOS non possono installare script negli Application Scripts da sole. Hanno bisogno del consenso dell’utente.
Un modo possibile per farlo è abilitare Read/Write
e mostrare una finestra di dialogo usando NSOpenPanel
per chiedere all’utente di selezionare la cartella in cui installare i nostri script.
Per XcodeWay, ho scelto di fornire uno script shell di installazione così l’utente ha un modo veloce per installare gli script.
#!/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 è molto potente. Tutto questo è reso esplicito così che l’utente ha il controllo completo su quali cose possono essere fatte.
Come un’estensione, uno script è fatto in modo asincrono in un processo diverso usando XPC per la comunicazione tra processi. Questo aumenta la sicurezza in quanto uno script non ha accesso allo spazio degli indirizzi della nostra app o estensione.
Più sicurezza in macOS Mojave
Quest’anno, al WWDC 2018, Apple ha introdotto macOS Mojave che si concentra su molti miglioramenti della sicurezza. Nella sezione Your Apps and the Future of macOS Security possiamo saperne di più sui nuovi requisiti di sicurezza per le app di macOS. Uno di questi è la descrizione d’uso per AppleEvents.
impossibile caricare le eccezioni info.plist (egpu overrides)
Abbiamo usato dichiarare la descrizione d’uso per molte autorizzazioni in iOS, come la libreria fotografica, la fotocamera e le notifiche push. Ora dobbiamo dichiarare la descrizione d’uso per AppleEvents.
La prima volta che la nostra estensione cerca di eseguire alcuni comandi AppleScript, viene mostrata la finestra di dialogo di cui sopra per chiedere il consenso dell’utente. L’utente può concedere o negare il permesso, ma per Xcode si prega di dire sì?
La soluzione per noi è dichiarare NSAppleEventsUsageDescription
nel target della nostra app. Abbiamo solo bisogno di dichiarare nel target dell’app, non nel target dell’estensione.
<key>NSAppleEventsUsageDescription</key><string>Use AppleScript to open folders</string>
Dove andare da qui
Huff huff, whew! Grazie per aver seguito un viaggio così lungo. Realizzare framework e strumenti richiede molto tempo, specialmente i plugin e le estensioni – dobbiamo cambiare continuamente per adattarli ai nuovi sistemi operativi e ai requisiti di sicurezza. Ma è un processo gratificante, perché abbiamo imparato di più e abbiamo alcuni strumenti per risparmiare il nostro tempo prezioso.
Per tuo riferimento, ecco le mie estensioni che sono completamente open source.
- XcodeWay
- XcodeColorSense2