Articles

Cum să vă convertiți plugin-urile Xcode în extensii Xcode

de Khoa Pham

Sursa: Imgur

Xcode este un IDE indispensabil pentru dezvoltatorii iOS și macOS. Încă din primele zile, capacitatea de a construi și de a instala plugin-uri personalizate ne-a oferit un impuls uriaș în ceea ce privește productivitatea. Nu a trecut mult timp până când Apple a introdus extensia Xcode din cauza preocupărilor legate de confidențialitate.

Am construit câteva pluginuri și extensii Xcode, cum ar fi XcodeWay, XcodeColorSense, XcodeColorSense2 și Xmas. A fost o experiență plină de satisfacții. Am învățat multe, iar productivitatea pe care am câștigat-o a fost considerabilă. În această postare am parcurs modul în care mi-am convertit plugin-urile Xcode în extensii și experiența pe care am avut-o făcând acest lucru.

Primul meu plugin Xcode: XcodeWay

Am ales o persoană leneșă pentru a face o treabă grea. Pentru că o persoană leneșă va găsi o modalitate ușoară de a o face

Îmi place foarte mult citatul de mai sus de la Bill Gates. Încerc să evit sarcinile repetitive și plictisitoare. Ori de câte ori mă trezesc că fac aceleași sarcini din nou, scriu scripturi și instrumente pentru a automatiza acest lucru. A face acest lucru necesită ceva timp, dar voi fi un pic mai leneș în viitorul apropiat.

Pe lângă interesul de a construi cadre și instrumente open source, îmi place să extind IDE-ul pe care îl folosesc – în principal Xcode.

Am început să dezvolt iOS în 2014. Am vrut o modalitate rapidă de a naviga în multe locuri direct din Xcode cu contextul proiectului curent. Există multe momente în care dorim să:

  • deschidem folderul curent al proiectului în „Finder” pentru a modifica unele fișiere
  • deschidem Terminal pentru a rula unele comenzi
  • deschidem fișierul curent în GitHub pentru a da rapid link-ul unui coleg de lucru
  • sau pentru a deschide alte foldere, cum ar fi teme, pluginuri, fragmente de cod, jurnale de dispozitiv.

Care puțin timp pe care îl economisim în fiecare zi contează.

M-am gândit că ar fi o idee mișto să scriu un plugin Xcode care să putem face toate lucrurile de mai sus chiar în Xcode. În loc să aștept ca alți oameni să o facă, mi-am suflecat mânecile și am scris primul meu plugin Xcode – XcodeWay- și l-am partajat ca sursă deschisă.

XcodeWay funcționează prin crearea unui meniu sub Editor cu o mulțime de opțiuni pentru a naviga în alte locuri direct din Xcode. Pare simplu, dar a fost nevoie de ceva muncă asiduă.

Ce sunt pluginurile Xcode?

Pixinele Xcode nu sunt suportate oficial de Xcode sau recomandate de Apple. Nu există documente despre ele. Cele mai bune locuri în care putem afla despre ele sunt prin intermediul codului sursă al pluginurilor existente și câteva tutoriale.

Un plugin Xcode este doar un pachet de tip xcplugin și este plasat la ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins . Xcode, la pornire, va încărca toate plugin-urile Xcode prezente în acest folder. Plugin-urile sunt rulate în același proces ca și Xcode, deci ar putea face orice ca și Xcode. O eroare în orice plugin poate provoca blocarea Xcode.

Pentru a realiza un plugin Xcode, creați un macOS Bundle cu o clasă care să se extindă din NSObject , și să aibă un inițializator care acceptă NSBundle , de exemplu în Xmas:

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

În interiorul Info.plist, trebuie să:

  • declarați această clasă ca fiind clasa principală de intrare pentru plugin, și
  • că acest pachet nu are UI, deoarece creăm controale UI și adăugăm la interfața Xcode în timpul execuției
<key>NSPrincipalClass</key><string>Xmas</string><key>XCPluginHasUI</key><false/>

O altă problemă cu pluginurile Xcode este că trebuie să actualizăm continuu DVTPluginCompatibilityUUIDs . Acest lucru se schimbă de fiecare dată când apare o nouă versiune de Xcode. Fără actualizare, Xcode va refuza să încarce plugin-ul.

Ce pot face plugin-urile Xcode

Mulți dezvoltatori construiesc plugin-uri Xcode pentru că le lipsesc caracteristicile specifice găsite în alte IDE-uri, cum ar fi Sublime Text, AppCode sau Atom.

Din moment ce plugin-urile Xcode sunt încărcate în același proces ca și Xcode, ele pot face tot ceea ce poate face Xcode. Singura limită este imaginația noastră. Putem profita de Objective C Runtime pentru a descoperi cadre și funcții private. Apoi, LLDB și Symbolic breakpoint pot fi utilizate în continuare pentru a inspecta codul în execuție și a le modifica comportamentele. De asemenea, putem utiliza swizzling pentru a modifica implementarea oricărui cod în execuție. Scrierea de plugin-uri Xcode este dificilă – multe presupuneri și, uneori, este necesară o bună cunoaștere a asamblării.

În epoca de aur a plugin-urilor, a existat un manager de plugin-uri popular, care era el însuși un plugin, numit Alcatraz. Acesta putea instala alte plugin-uri, care practic doar descărca fișierul xcplugin și muta acest lucru în folderul Plug Ins.

Pentru a ne face o idee despre ce pot face plugin-urile, să aruncăm o privire la câteva plugin-uri populare.

Xvim

Primul din listă este Xvim, care adaugă keybindings Vim chiar în Xcode. Suportă în mare parte toate legăturile de taste pe care obișnuiam să le avem în Terminal.

SCXcodeMiniMap

Dacă vă lipsește modul MiniMap din Sublime Text, puteți folosi SCXcodeMiniMap pentru a adăuga un panou de hartă dreapta în interiorul editorului Xcode.

FuzzyAutocompletePlugin

Înainte de versiunea 9, Xcode nu avea o completare automată adecvată – se baza doar pe prefix. Aici a fost punctul în care FuzzyAutocompletePlugin a strălucit. Acesta realizează autocompletarea fuzzy pe baza caracteristicii IDEOpenQuicklyPattern ascunse în Xcode.

KSImageNamed-Xcode

Pentru a afișa o imagine a pachetului în interiorul UIImageView, folosim adesea metoda imageNamed. Dar este greu să ne amintim exact numele fișierului de imagine. KSImageNamed-Xcode este aici pentru a vă ajuta. Veți primi o listă de nume de imagini auto-sugestionate atunci când începeți să tastați.

ColorSense-for-Xcode

O altă mâncărime în timpul dezvoltării este de a lucra cu UIColor , care folosește spațiul de culoare RGBA. Nu obținem un indicator vizual al culorii pe care o specificăm, iar efectuarea manuală a verificării poate consuma mult timp. Din fericire, există ColorSense-for-Xcode, care arată culoarea utilizată și panoul de selectare a culorilor pentru a selecta cu ușurință culoarea corectă.

LinkedConsole

În AppCode, putem sări la o anumită linie din fișier care este înregistrată în interiorul consolei. Dacă vă lipsește această funcție în Xcode, puteți utiliza LinkedConsole. Aceasta activează link-uri pe care se poate face clic în interiorul consolei Xcode, astfel încât să putem sări la acel fișier instantaneu.

Lucrul greu din spatele plugin-urilor Xcode

Realizarea unui plugin Xcode nu este ușoară. Nu numai că trebuie să cunoaștem programarea macOS, dar trebuie, de asemenea, să ne scufundăm adânc în ierarhia de vizualizare Xcode. Trebuie să explorăm cadrele și API-urile private pentru a injecta caracteristica pe care o dorim.

Există foarte puține tutoriale despre cum să facem plugin-uri, dar, din fericire, majoritatea plugin-urilor sunt open source, astfel încât putem înțelege cum funcționează. Având în vedere că am realizat câteva plugin-uri, pot da câteva detalii tehnice despre ele.

Pluginile Xcode sunt realizate de obicei cu două framework-uri private: DVTKit și IDEKit . Cadrele de sistem sunt la /System/Library/PrivateFrameworks dar cadrele pe care Xcode le folosește exclusiv sunt la /Applications/Xcode.app/Contents/ , acolo se găsesc Frameworks , OtherFrameworks și SharedFrameworks.

Există un instrument class-dump care poate genera anteturi din pachetul de aplicații Xcode. Cu numele clasei și metodele, puteți apela NSClassFromString pentru a obține clasa din nume.

Swizzling DVTBezelAlertPanel framework in Xmas

Crăciunul mi-a dat întotdeauna un sentiment special, așa că am decis să fac Xmas, care arată o imagine aleatorie de Crăciun în loc de vizualizarea implicită a alertei. Clasa utilizată pentru a reda această vizualizare este DVTBezelAlertPanel din cadrul DVTKit. Articolul meu despre construirea acelui plugin este aici.

Cu Objective C Runtime, există o tehnică numită swizzling, care poate schimba și schimba implementarea și semnătura de metodă a oricărei clase și metode care rulează.

Aici, pentru a schimba conținutul acelei vizualizări de alertă, trebuie să schimbăm inițializatorul initWithIcon:message:parentWindow:duration: cu propria noastră metodă. Facem acest lucru mai devreme, ascultând NSApplicationDidFinishLaunchingNotification care este notificat atunci când un plugin macOS, în acest caz Xcode, se lansează.

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") }}

Început mi-a plăcut să fac totul în Swift. Dar este complicat să folosești metoda swizzle init în Swift, așa că cel mai rapid mod este să faci asta în Objective C. Apoi, pur și simplu traversăm ierarhia de vizualizare pentru a găsi NSVisualEffectView în interiorul NSPanel pentru a actualiza imaginea.

Interacțiunea cu DVTSourceTextView în XcodeColorSense

Lucrez în principal cu culori hexagonale și vreau o modalitate rapidă de a vedea culoarea. Așa că am construit XcodeColorSense – suportă culoarea hexagonală, RGBA și culoarea numită.

Ideea este simplă. Analizați șirul pentru a vedea dacă utilizatorul tastează ceva legat de UIColor și afișați o mică vizualizare suprapusă cu acea culoare ca fundal. Vizualizarea textului pe care Xcode o folosește este de tip DVTSourceTextView în cadrul DVTKit. De asemenea, trebuie să ascultăm NSTextViewDidChangeSelectionNotification care este declanșat ori de câte ori se modifică orice conținut NSTextView.

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}

Am avut o arhitectură Matcher astfel încât să putem detecta diferite tipuri de construcții UIColor – de exemplu 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) }}

Pentru a reda suprapunerea, folosim NSColorWell care este bun pentru a afișa o vedere cu fundal. Poziția este determinată prin apelarea firstRectForCharacterRange și câteva conversii de puncte cu convertRectFromScreen și convertRect .

Utilizarea NSTask și IDEWorkspaceWindowController în XcodeWay

În sfârșit, iubitul meu XcodeWay.

M-am trezit că trebuie să merg în diferite locuri din Xcode cu contextul proiectului curent. Așa că am construit XcodeWay ca un plugin care adaugă o mulțime de opțiuni de meniu la îndemână sub Window.

Din moment ce pluginul rulează în același proces Xcode, are acces la meniul principal NSApp.mainMenu?.itemWithTitle("Window") . Acolo putem modifica meniul. XcodeWay este conceput pentru a extinde cu ușurință funcționalitățile prin protocolul său Navigator.

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

Pentru folderele cu o cale statică, cum ar fi Provisioning Profile ~/Library/MobileDevice/Provisioning Profiles sau User data Developer/Xcode/UserData , putem doar să construim URL și să apelăm NSWorkspace.sharedWorkspace().openURL . Pentru folderele dinamice care variază în funcție de proiectul curent, trebuie să se lucreze mai mult.

Cum deschidem folderul pentru proiectul curent în Finder? Informațiile pentru calea proiectului curent sunt păstrate în interiorul IDEWorkspaceWindowController . Aceasta este o clasă care gestionează ferestrele spațiului de lucru în Xcode. Aruncați o privire la EnvironmentManager unde folosim objc_getClass pentru a obține definiția clasei dintr-un șir de caractere.

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

În cele din urmă, putem utiliza valueForKey pentru a obține valoarea pentru orice proprietate care credem că există. În acest fel, nu numai că obținem calea proiectului, ci și calea către fișierul de deschidere. Astfel, putem apela activateFileViewerSelectingURLs la NSWorkspace pentru a deschide Finder cu acel fișier selectat. Acest lucru este la îndemână, deoarece utilizatorii nu trebuie să caute acel fișier în Finder.

De multe ori dorim să executăm unele comenzi Terminal pe folderul curent al proiectului. Pentru a realiza acest lucru, putem folosi NSTask cu pad-ul de lansare /usr/bin/open și argumentele . iTerm, dacă este configurat probabil, va deschide acest lucru într-o filă nouă.

Documentele pentru aplicațiile iOS 7 sunt plasate în locația fixă iPhone Simulator în interiorul Application Support. Dar, începând cu iOS 8, fiecare aplicație are un UUID unic, iar dosarele lor de documente sunt greu de prevăzut.

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

Potem să construim o hartă și să efectuăm urmărirea pentru a găsi ID-ul generat pentru proiectul curent sau să verificăm plist-ul din interiorul fiecărui dosar pentru a compara identificatorul pachetului.

Soluția rapidă pe care am găsit-o a fost să caut cel mai recent dosar actualizat. De fiecare dată când construim proiectul sau facem modificări în interiorul aplicației, dosarul lor de documente este actualizat. De aici ne putem folosi de NSFileModificationDate pentru a găsi dosarul pentru proiectul curent.

Există multe hack-uri atunci când lucrăm cu plugin-urile Xcode, dar rezultatele sunt răsplătitoare. Fiecare câteva minute pe care le economisim în fiecare zi sfârșesc prin a economisi mult timp în general.

Securitate și libertate

Cu o mare putere vine o mare responsabilitate. Faptul că plugin-urile pot face tot ce vor sună ca o alertă la adresa securității. La sfârșitul anului 2015, a avut loc un atac malware prin distribuirea unei versiuni modificate a Xcode, numită XcodeGhost, care injectează cod malițios în orice aplicație construită cu Xcode Ghost. Se crede că malware-ul folosește, printre altele, mecanismul plugin.

Ca și aplicațiile iOS pe care le descărcăm din Appstore, aplicațiile macOS, cum ar fi Xcode, sunt semnate de Apple atunci când le descărcăm din Mac Appstore sau prin intermediul link-urilor oficiale de descărcare Apple.

Semnarea codului asigură utilizatorii că acesta provine dintr-o sursă cunoscută și că aplicația nu a fost modificată de când a fost semnată ultima dată. Înainte ca aplicația dvs. să poată integra servicii de aplicații, să fie instalată pe un dispozitiv sau să fie trimisă în App Store, trebuie să fie semnată cu un certificat emis de Apple

Pentru a evita potențialele programe malware de acest tip, la WWDC 2016, Apple a anunțat extensia Xcode Source Editor Extension ca singura modalitate de a încărca extensii terțe în Xcode. Acest lucru înseamnă că, începând cu Xcode 8, plugin-urile nu mai pot fi încărcate.

Source Editor Extension

Extensia este abordarea recomandată pentru a adăuga în siguranță funcționalități în moduri restricționate.

Extensiile de aplicații oferă utilizatorilor acces la funcționalitatea și conținutul aplicației dvs. în întregul iOS și macOS. De exemplu, aplicația dvs. poate să apară acum ca widget pe ecranul Today (Astăzi), să adauge noi butoane în foaia Action (Acțiune), să ofere filtre foto în cadrul aplicației Photos (Fotografii) sau să afișeze o nouă tastatură personalizată la nivelul întregului sistem.

Deocamdată, singura extensie pentru Xcode este Source Editor (Editor de surse), care ne permite să citim și să modificăm conținutul unui fișier sursă, precum și să citim și să modificăm selecția de text curentă în cadrul editorului.

Extensiunea este o țintă nouă și rulează într-un proces diferit de Xcode. Acest lucru este bun în sensul că nu poate modifica Xcode în alte moduri decât conformându-se la XCSourceEditorCommand pentru a modifica conținutul curent al documentului.

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

Xcode 8 are o mulțime de îmbunătățiri, cum ar fi noile caracteristici de completare a codului, literalele de imagine și culoare Swift și snippets. Acest lucru a dus la deprecierea multor plugin-uri Xcode. Pentru unele pluginuri indispensabile, cum ar fi XVim, acest lucru este insuportabil pentru unele persoane. Unele caracteristici vechi ale plugin-urilor nu pot fi realizate cu actualul sistem Source Editor Extension.

Dacă nu renunțați la Xcode

O soluție pentru a ocoli restricția din Xcode 8 pentru plugin-uri, este înlocuirea semnăturii Xcode existente printr-o tehnică numită resign. Demisia este foarte ușoară – trebuie doar să creăm un certificat auto-semnat și să apelăm comanda codesign. După aceasta, Xcode ar trebui să fie capabil să încarce plugin-uri.

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

Nu este, totuși, posibilă trimiterea aplicațiilor construite cu Xcode resemnat, deoarece semnătura nu se potrivește cu versiunea oficială a Xcode. O modalitate este de a folosi două Xcode: unul oficial pentru distribuție și unul resemnat pentru dezvoltare.

Mutarea la extensia Xcode

Extensiunea Xcode este calea de urmat, așa că am început să-mi mut plugin-urile la extensie. Pentru Xmas, deoarece modifică ierarhia de vizualizare, nu poate deveni o extensie.

Color literal în XcodeColorSense2

Pentru simțul culorii, am rescris extensia de la zero și am numit-o XcodeColorSense2. Aceasta, bineînțeles, nu poate afișa o suprapunere peste vizualizarea curentă a editorului. Așa că am ales să folosesc noul Color literal găsit în Xcode 8+.

Culoarea este afișată într-o căsuță mică. Poate fi greu de distins culori similare, de aceea am inclus și numele. Codul se referă pur și simplu la inspectarea selections și analizarea pentru a găsi declarația de culoare.

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) }}

Majoritatea funcționalității este încorporată în cadrul meu Farge, dar nu găsesc o modalitate de a utiliza cadrul în interiorul extensiei Xcode.

Din moment ce funcția de extensie este accesibilă doar prin meniul Editor, putem personaliza o legătură de taste pentru a invoca acest element de meniu. De exemplu, eu aleg Cmd+Ctrl+S pentru a afișa și ascunde informațiile despre culori.

Acest lucru nu este, desigur, intuitiv în comparație cu pluginul original, dar este mai bine decât nimic.

Cum să depanăm extensiile Xcode

Funcționarea și depanarea extensiilor este simplă. Putem folosi Xcode pentru a depana Xcode. Versiunea depanată a Xcode are o pictogramă gri.

Cum se instalează extensiile Xcode

Extensia trebuie să aibă o aplicație macOS însoțitoare. Aceasta poate fi distribuită la Mac Appstore sau autosemnată. Am scris un articol despre cum se face acest lucru.

Toate extensiile pentru o aplicație trebuie să fie activate în mod explicit prin „System Preferences”.

Extensia Xcode funcționează deocamdată doar cu editorul, deci trebuie să deschidem un fișier sursă pentru ca meniul Editor să aibă efect.

AppleScript în XcodeWay

În extensiile Xcode, NSWorkspace, NSTask și construcția de clase private nu mai funcționează. Din moment ce am folosit extensia Finder Sync în FinderGo, m-am gândit că aș putea încerca același scripting AppleScript pentru extensia Xcode.

AppleScript este un limbaj de scripting creat de Apple. Acesta permite utilizatorilor să controleze direct aplicațiile Macintosh care pot fi scriptate, precum și părți din macOS însuși. Puteți crea scripturi – seturi de instrucțiuni scrise – pentru a automatiza sarcini repetitive, pentru a combina caracteristici din mai multe aplicații cu scripturi și pentru a crea fluxuri de lucru complexe.

Pentru a încerca AppleScript, puteți utiliza aplicația Script Editor încorporată în macOS pentru a scrie funcții prototip. Declarația funcției începe cu on și se termină cu end . Pentru a evita potențialele conflicte cu funcțiile de sistem, eu folosesc de obicei my ca prefix. Iată cum mă bazez pe System Events pentru a obține directorul de acasă.

Terminologia scripturilor de interfață utilizator se găsește în „Processes Suite” din dicționarul de scripturi „System Events”. Această suită include terminologia pentru interacțiunea cu majoritatea tipurilor de elemente de interfață utilizator, inclusiv:

  • ferestre
  • botoane
  • case de selectare
  • meniuri
  • botoane radio
  • câmpuri de text.

În Evenimente de sistem, clasa process reprezintă o aplicație care rulează.

Multe aplicații de bun cetățean sprijină AppleScript prin expunerea unora dintre funcționalitățile lor, astfel încât acestea să poată fi utilizate de alte aplicații. Iată cum obțin melodia curentă de la Spotify în 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

Pentru a obține toate comenzile posibile ale unei anumite aplicații, putem deschide dicționarul în Script Editor. Acolo putem afla ce funcții și parametri sunt suportați.

Dacă credeți că Objective C este greu, AppleScript este mult mai greu. Sintaxa este verboasă și predispusă la erori. Pentru referință, iată întregul fișier script care alimentează XcodeWay.

Pentru a deschide un anumit dosar, spuneți-i lui Finder folosind POSIX file. Am refactorizat fiecare funcționalitate în funcție pentru o mai bună reutilizare a codului.

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

Apoi, pentru a rula AppleScript în interiorul unei aplicații sau extensii macOS, trebuie să construim un descriptor AppleScript cu numărul de serie al procesului și identificatorii de eveniment corecți.

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}

Alte sarcini, cum ar fi verificarea telecomenzii Git curente, sunt un pic mai complicate. De multe ori vreau să împărtășesc colegului meu de echipă de la distanță link-ul fișierului pe care îl depanez, astfel încât acesta să știe la ce fișier fac referire. Acest lucru se poate face folosind shell script în interiorul 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

Potem folosi quoted și concatenarea șirurilor de caractere pentru a forma șiruri de caractere. Din fericire, putem expune cadrul Foundation și anumite clase. Iată cum expun NSString pentru a profita de toate funcționalitățile existente. Scrierea manipulării șirurilor de caractere de la zero folosind AppleScript simplu va dura foarte mult timp.

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

Cu aceasta putem construi celelalte funcții ale noastre pentru manipularea șirurilor de caractere.

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

O caracteristică interesantă pe care XcodeWay o suportă este posibilitatea de a merge la directorul de documente pentru aplicația curentă în simulator. Acest lucru este la îndemână atunci când trebuie să inspectăm un document pentru a verifica datele salvate sau stocate în memoria cache. Directorul este dinamic, deci este greu de detectat. Putem, totuși, să sortăm directorul pentru cel mai recent actualizat. Mai jos este modul în care înlănțuim mai multe comenzi shell scripts pentru a găsi directorul.

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

Această caracteristică m-a ajutat foarte mult atunci când am dezvoltat Gallery pentru a verifica dacă videoclipurile și imaginile descărcate sunt salvate în locul corect.

Dar, niciunul dintre scripturi nu pare să funcționeze. Scripturile au făcut întotdeauna parte din macOS încă din 1993. Dar, odată cu apariția Mac Appstore și cu preocupările legate de securitate, AppleScript a fost în cele din urmă restricționat la mijlocul anului 2012. Atunci a fost momentul în care a fost impus App Sandbox.

App Sandbox

App Sandbox este o tehnologie de control al accesului furnizată în macOS, impusă la nivel de kernel. Aceasta este concepută pentru a limita daunele aduse sistemului și datelor utilizatorului în cazul în care o aplicație devine compromisă. Aplicațiile distribuite prin Mac App Store trebuie să adopte App Sandbox.

Pentru ca o extensie Xcode să fie încărcată de Xcode, aceasta trebuie să susțină, de asemenea, App Sandbox.

La începutul aplicării App Sandbox, am putea folosi App Sandbox Temporary Exception pentru a acorda temporar aplicației noastre acces la Apple Script.

Acum acest lucru nu mai este posibil.

Singurul mod în care AppleScript poate rula este dacă se află în interiorul folderului ~/Library/Application Scripts.

Cum să instalați scripturi personalizate

Aplicațiile sau extensiile MacOS nu pot instala singure scripturi în Application Scripts. Ele au nevoie de consimțământul utilizatorului.

O posibilă modalitate de a face acest lucru este de a activa Read/Write și de a afișa o fereastră de dialog folosind NSOpenPanel pentru a cere utilizatorului să selecteze folderul în care să instaleze scripturile noastre.

Pentru XcodeWay, am ales să furnizez un script shell de instalare pentru ca utilizatorul să aibă o modalitate rapidă de a instala scripturile.

#!/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 este foarte puternic. Toate acestea sunt explicite, astfel încât utilizatorul are un control complet asupra lucrurilor care pot fi făcute.

Ca o extensie, un script este realizat în mod asincron într-un proces diferit, folosind XPC pentru comunicarea între procese. Acest lucru sporește securitatea, deoarece un script nu are acces la spațiul de adrese la aplicația sau extensia noastră.

Mai multă securitate în macOS Mojave

În acest an, la WWDC 2018, Apple a prezentat macOS Mojave care se concentrează pe o mulțime de îmbunătățiri de securitate. În secțiunea Your Apps and the Future of macOS Security (Aplicațiile tale și viitorul securității macOS) putem afla mai multe despre noile cerințe de securitate pentru aplicațiile macOS. Una dintre ele este descrierea utilizării pentru AppleEvents.

unable to load info.plist exceptions (egpu overrides)

Suntem obișnuiți să declarăm descrierea utilizării pentru multe permisiuni în iOS, cum ar fi biblioteca foto, camera și notificările push. Acum trebuie să declarăm descrierea utilizării pentru AppleEvents.

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

Prima dată când extensia noastră încearcă să execute unele comenzi AppleScript, se afișează fereastra de dialog de mai sus pentru a cere consimțământul utilizatorului. Utilizatorul poate acorda sau refuza permisiunea, dar pentru Xcode vă rugăm să spuneți „da” ?

Repararea pentru noi este de a declara NSAppleEventsUsageDescription în ținta aplicației noastre. Trebuie să declarăm doar în ținta aplicației, nu și în ținta extensiei.

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

Unde să mergem de aici

Huff huff, whew! Vă mulțumim că ați urmărit o călătorie atât de lungă. Realizarea cadrelor și a instrumentelor necesită mult timp, în special a pluginurilor și a extensiilor – trebuie să le modificăm continuu pentru a le adapta la noile sisteme de operare și cerințe de securitate. Dar este un proces plin de satisfacții, deoarece am învățat mai mult și avem niște instrumente care să ne salveze timpul prețios.

Pentru referință, iată extensiile mele care sunt complet open source.

  • XcodeWay
  • XcodeColorSense2