Articles

Hur du konverterar dina Xcode-plugins till Xcode-tillägg

av Khoa Pham

Källa: Imgur

Xcode är ett oumbärligt IDE för iOS- och macOS-utvecklare. Redan från början hade möjligheten att bygga och installera anpassade plugins gett oss en enorm ökning av produktiviteten. Det dröjde inte länge innan Apple införde Xcode-tillägg på grund av integritetsfrågor.

Jag har byggt några Xcode-plugins och tillägg som XcodeWay, XcodeColorSense, XcodeColorSense2 och Xmas. Det var en givande erfarenhet. Jag lärde mig mycket och den produktivitet jag fick var betydande. I det här inlägget går jag igenom hur jag konverterade mina Xcode-plugins till tillägg, och vilken erfarenhet jag hade av att göra det.

Mitt första Xcode-plugin: XcodeWay

Jag väljer en lat person för att göra ett svårt jobb. Eftersom en lat person kommer att hitta ett enkelt sätt att göra det

Jag gillar verkligen ovanstående citat från Bill Gates. Jag försöker undvika repetitiva och tråkiga uppgifter. När jag upptäcker att jag gör samma uppgifter igen skriver jag skript och verktyg för att automatisera det. Att göra detta tar lite tid, men jag kommer att bli lite latare inom en snar framtid.

Bortsett från intresset för att bygga ramverk och verktyg med öppen källkod gillar jag att utöka det IDE jag använder – mestadels Xcode.

Jag började med iOS-utveckling för första gången 2014. Jag ville ha ett snabbt sätt att navigera till många ställen direkt från Xcode med sammanhanget för det aktuella projektet. Det finns många gånger vi vill:

  • öppna den aktuella projektmappen i ”Finder” för att ändra några filer
  • öppna Terminal för att köra några kommandon
  • öppna den aktuella filen i GitHub för att snabbt ge länken till en arbetskamrat
  • eller för att öppna andra mappar som teman, plugins, kodutdrag, enhetsloggar.

Varje liten bit tid vi sparar varje dag räknas.

Jag tänkte att det vore en häftig idé att skriva ett Xcode-plugin som vi kan göra alla ovanstående saker direkt i Xcode. Istället för att vänta på att andra ska göra det drog jag upp ärmen och skrev mitt första Xcode-plugin – XcodeWay- och delade det som öppen källkod.

XcodeWay fungerar genom att skapa en meny under Editor med massor av alternativ för att navigera till andra platser direkt från Xcode. Det ser enkelt ut men det krävdes en del hårt arbete.

Vad är Xcode-plugins?

Xcode-plugins stöds inte officiellt av Xcode och rekommenderas inte av Apple. Det finns inga dokument om dem. De bästa ställena vi kan lära oss om dem är via befintliga plugins källkod och några få handledningar.

En Xcode-plugin är bara ett paket av typen xcplugin och är placerad på ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins . När Xcode startar laddas alla Xcode-plugins som finns i denna mapp. Plugins körs i samma process som Xcode, så de kan göra allt som Xcode. Ett fel i en insticksmodul kan få Xcode att krascha.

För att göra ett Xcode-plugin skapar du en macOS Bundle med en klass som sträcker sig från NSObject , och har en initialiserare som accepterar NSBundle , till exempel i Xmas:

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

Inom Info.plist behöver vi:

  • deklarera den här klassen som huvudklass för insticksprogrammet, och
  • att det här paketet inte har något användargränssnitt, eftersom vi skapar användargränssnittskontroller och lägger till Xcode-gränssnittet under körning
<key>NSPrincipalClass</key><string>Xmas</string><key>XCPluginHasUI</key><false/>

Ett annat problem med Xcode-insticksprogram är att vi måste uppdatera DVTPluginCompatibilityUUIDs kontinuerligt. Detta ändras varje gång en ny version av Xcode kommer ut. Utan uppdatering vägrar Xcode att läsa in insticksprogrammet.

Vad Xcode-insticksprogram kan göra

Många utvecklare bygger Xcode-insticksprogram eftersom de saknar specifika funktioner som finns i andra IDE:er som Sublime Text, AppCode eller Atom.

Då Xcode-insticksprogram laddas i samma process som Xcode, kan de göra allt som Xcode kan. Den enda gränsen är vår fantasi. Vi kan utnyttja Objective C Runtime för att upptäcka privata ramverk och funktioner. Sedan kan LLDB och Symbolic breakpoint användas ytterligare för att inspektera löpande kod och ändra deras beteenden. Vi kan också använda swizzling för att ändra implementeringen av en pågående kod. Att skriva Xcode-plugins är svårt – mycket gissningar och ibland krävs goda kunskaper i assembler.

I plugins guldålder fanns det en populär pluginhanterare, som i sig själv var ett plugin, som hette Alcatraz. Den kunde installera andra plugins, som i princip bara hämtar xcplugin-filen och flyttar den till mappen Plug Ins.

För att få en uppfattning om vad plugins kan göra ska vi ta en titt på några populära plugins.

Xvim

Först på listan är Xvim, som lägger till Vim-tangentbindningar direkt i Xcode. Den har stöd för nästan alla tangentbindningar som vi brukade ha i Terminal.

SCXcodeMiniMap

Om du saknar MiniMap-läget i Sublime Text kan du använda SCXcodeMiniMap för att lägga till en rätt kartpanel inne i Xcode-redigeraren.

FuzzyAutocompletePlugin

För version 9 hade Xcode ingen riktig automatisk komplettering – den var bara baserad på prefix. Det var där FuzzyAutocompletePlugin glänste. Den utför fuzzy automatisk komplettering baserat på den dolda IDEOpenQuicklyPattern-funktionen i Xcode.

KSImageNamed-Xcode

För att visa en buntbild inuti UIImageView använder vi ofta imageNamed-metoden. Men det är svårt att komma ihåg exakt namnet på bildfilen. KSImageNamed-Xcode är här för att hjälpa till. Du får en lista med automatiskt föreslagna bildnamn när du börjar skriva.

ColorSense-for-Xcode

En annan klåda under utvecklingen är att arbeta med UIColor , som använder RGBA-färgrymd. Vi får ingen visuell indikator på den färg som vi anger, och att manuellt utföra kontroller kan vara tidskrävande. Som tur är finns ColorSense-for-Xcode som visar vilken färg som används och färgväljarpanelen för att enkelt välja rätt färg.

LinkedConsole

I AppCode kan vi hoppa till en specifik rad i filen som loggas inne i konsolen. Om du saknar den här funktionen i Xcode kan du använda LinkedConsole. Detta aktiverar klickbara länkar inuti Xcode-konsolen så att vi kan hoppa till filen direkt.

Det hårda arbetet bakom Xcode-plugins

Att göra ett Xcode-plugin är inte lätt. Vi behöver inte bara kunna macOS-programmering, utan vi måste också dyka djupt ner i Xcodes visningshierarki. Vi måste utforska privata ramverk och API:er för att injicera den funktion vi vill ha.

Det finns väldigt få handledningar om hur man gör plugins, men som tur är är de flesta plugins öppen källkod så att vi kan förstå hur de fungerar. Eftersom jag har gjort några plugins kan jag ge några tekniska detaljer om dem.

Xcode-plugins görs vanligtvis med två privata ramverk: DVTKit och IDEKit . Systemramarna finns på /System/Library/PrivateFrameworks men ramarna som Xcode uteslutande använder ligger under /Applications/Xcode.app/Contents/ , där hittar du Frameworks , OtherFrameworks och SharedFrameworks.

Det finns ett verktyg class-dump som kan generera headers från Xcode app bundle. Med klassnamnen och metoderna kan du ringa NSClassFromString för att få klassen från namnet.

Swizzling DVTBezelAlertPanel-ramverket i Xmas

Julen har alltid gett mig en speciell känsla, så jag bestämde mig för att göra Xmas, som visar en slumpmässig julbild istället för standardvarningsvyn. Klassen som används för att rendera den vyn är DVTBezelAlertPanel inom ramen för DVTKit. Min artikel om att bygga det insticksprogrammet finns här.

Med Objective C Runtime finns det en teknik som kallas swizzling, som kan ändra och byta implementering och metodsignatur för alla pågående klasser och metoder.

Här, för att ändra innehållet i den där varningsvyn, måste vi byta ut initialiseraren initWithIcon:message:parentWindow:duration: med vår egen metod. Det gör vi tidigt genom att lyssna på NSApplicationDidFinishLaunchingNotification som meddelas när ett macOS-plugin, i det här fallet Xcode, startar.

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

I början gillade jag att göra allt i Swift. Men det är knepigt att använda swizzle init-metoden i Swift, så det snabbaste sättet är att göra det i Objective C. Sedan går vi helt enkelt igenom visningshierarkin för att hitta NSVisualEffectView inne i NSPanel för att uppdatera bilden.

Interaktion med DVTSourceTextView i XcodeColorSense

Jag arbetar mestadels med hexafärger och jag vill ha ett snabbt sätt att se färgen. Så jag byggde XcodeColorSense – den har stöd för hexfärg, RGBA och namngiven färg.

Idén är enkel. Analysera strängen för att se om användaren skriver något som är relaterat till UIColor, och visa en liten överlagd vy med den färgen som bakgrund. Textvyn som Xcode använder är av typen DVTSourceTextView i DVTKit ramverket. Vi behöver också lyssna på NSTextViewDidChangeSelectionNotification som utlöses när något NSTextView-innehåll ändras.

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}

Jag hade en Matcher-arkitektur så att vi kan upptäcka olika typer av UIColor-konstruktioner – till exempel 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) }}

För att återskapa överlagret använder vi NSColorWell som är bra för att visa en vy med bakgrund. Positionen bestäms genom att kalla firstRectForCharacterRange och några punktkonverteringar med convertRectFromScreen och convertRect .

Användning av NSTask och IDEWorkspaceWindowController i XcodeWay

Äntligen, min älskade XcodeWay.

Jag upptäckte att jag behövde gå till olika ställen från Xcode med sammanhanget för det aktuella projektet. Så jag byggde XcodeWay som ett plugin som lägger till många praktiska menyalternativ under Window.

Då pluginet körs i samma Xcode-process har det tillgång till huvudmenyn NSApp.mainMenu?.itemWithTitle("Window") . Där kan vi ändra menyn. XcodeWay är utformad för att enkelt utöka funktionaliteter genom sitt Navigator-protokoll.

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

För mappar med en statisk sökväg som Provisioning Profile ~/Library/MobileDevice/Provisioning Profiles eller Användardata Developer/Xcode/UserData , kan vi bara konstruera URL och anropa NSWorkspace.sharedWorkspace().openURL . För dynamiska mappar som varierar beroende på det aktuella projektet måste mer arbete göras.

Hur öppnar vi mappen för det aktuella projektet i Finder? Informationen om sökvägen för det aktuella projektet finns i IDEWorkspaceWindowController . Detta är en klass som hanterar arbetsrumsfönster i Xcode. Ta en titt på EnvironmentManager där vi använder objc_getClass för att hämta klassdefinitionen från en sträng.

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

Slutligt kan vi använda valueForKey för att hämta värdet för någon egenskap som vi tror finns. På detta sätt får vi inte bara projektets sökväg utan även sökvägen till öppningsfilen. Så vi kan anropa activateFileViewerSelectingURLsNSWorkspace för att öppna Finder med den filen vald. Detta är praktiskt eftersom användarna inte behöver leta efter den filen i Finder.

Många gånger vill vi utföra några Terminalkommandon på den aktuella projektmappen. För att åstadkomma detta kan vi använda NSTask med launch pad /usr/bin/open och argument . iTerm, om det är konfigurerat troligen, kommer att öppna detta i en ny flik.

Dokumenten för iOS 7-appar placeras på den fasta platsen iPhone Simulator inuti Application Support. Men från iOS 8 har varje app ett unikt UUID och deras dokumentmappar är svåra att förutsäga.

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

Vi kan bygga en karta och utföra spårning för att hitta det genererade ID:t för det aktuella projektet, eller för att kontrollera plist inuti varje mapp för att jämföra buntidentifieraren.

Den snabba lösningen som jag kom fram till var att söka efter den senast uppdaterade mappen. Varje gång vi bygger projektet eller gör ändringar inuti appen uppdateras deras dokumentmapp. Det är där vi kan använda NSFileModificationDate för att hitta mappen för det aktuella projektet.

Det finns många hacks när man arbetar med Xcode-plugins, men resultaten är givande. Varje par minuter vi sparar varje dag slutar med att vi sparar mycket tid totalt sett.

Säkerhet och frihet

Med stor makt följer stort ansvar. Det faktum att plugins kan göra vad de vill ringar in en varning för säkerheten. I slutet av 2015 skedde en attack med skadlig kod genom att distribuera en modifierad version av Xcode, kallad XcodeGhost, som injicerar skadlig kod i alla appar som byggs med Xcode Ghost. Den skadliga programvaran tros bland annat använda plugin-mekanismen.

Likt iOS-appar som vi laddar ner från Appstore signeras macOS-appar som Xcode av Apple när vi laddar ner dem från Mac Appstore eller via Apples officiella nedladdningslänkar.

Kodsignering av din app försäkrar användarna om att den kommer från en känd källa och att appen inte har ändrats sedan den senast signerades. Innan din app kan integrera apptjänster, installeras på en enhet eller skickas in till App Store måste den signeras med ett certifikat som utfärdats av Apple

För att undvika potentiell skadlig kodning av det här slaget meddelade Apple på WWDC 2016 att Xcode Source Editor Extension är det enda sättet att ladda in tillägg från tredje part i Xcode. Detta innebär att från Xcode 8 kan plugins inte laddas.

Source Editor Extension

Extension är det rekommenderade tillvägagångssättet för att på ett säkert sätt lägga till funktioner på begränsade sätt.

Apputvidgningar ger användarna tillgång till din apps funktionalitet och innehåll i hela iOS och macOS. Din app kan nu till exempel visas som en widget på Today-skärmen, lägga till nya knappar i Action sheet, erbjuda fotofilter i appen Photos eller visa ett nytt anpassat tangentbord för hela systemet.

För tillfället är det enda tillägget till Xcode Source Editor, som gör det möjligt att läsa och ändra innehållet i en källfil, samt läsa och ändra det aktuella textvalet i editorn.

Extension är ett nytt mål och körs i en annan process än Xcode. Detta är bra eftersom det inte kan ändra Xcode på något annat sätt än att det överensstämmer med XCSourceEditorCommand för att ändra det aktuella dokumentinnehållet.

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

Xcode 8 har många förbättringar som de nya funktionerna för kodkomplettering, Swift-bild- och färglitteraler och snippets. Detta har lett till att många Xcode-plugins har avvecklats. För vissa oumbärliga plugins som XVim är detta outhärdligt för vissa personer. Vissa gamla plugin-funktioner kan inte uppnås med det nuvarande Source Editor Extension-systemet.

Om du inte avstår från Xcode

En lösning för att kringgå begränsningen från Xcode 8 för plugins, är att ersätta den befintliga Xcode-signaturen med en teknik som kallas resign. Resignering är mycket enkelt – vi behöver bara skapa ett självsignerat certifikat och anropa kommandot codesign. Efter detta bör Xcode kunna ladda plugins.

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

Det är dock inte möjligt att skicka in appar som byggts med resignerat Xcode eftersom signaturen inte stämmer överens med den officiella versionen av Xcode. Ett sätt är att använda två Xcode: en officiell för distribution och en avskriven för utveckling.

Förflyttning till Xcode extension

Xcode extension är rätt väg att gå, så jag började flytta mina plugins till extension. För Xmas, eftersom det ändrar visningshierarkin, kan det inte bli ett tillägg.

Färglitteratur i XcodeColorSense2

För färgkänslan skrev jag om tillägget från grunden och kallade det XcodeColorSense2. Detta kan naturligtvis inte visa ett overlay över den aktuella redigeringsvyn. Därför valde jag att använda den nya Color literal som finns i Xcode 8+.

Färgen visas i en liten ruta. Det kan vara svårt att skilja liknande färger åt, så det är därför jag även inkluderar namnet. Koden handlar helt enkelt om att inspektera selections och analysera för att hitta färgdeklarationen.

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

De flesta funktionerna är inbäddade i mitt ramverk Farge, men jag kan inte hitta något sätt att använda ramverket i Xcode extension.

Då extension-funktionen endast är åtkomlig via Editorns meny, kan vi anpassa en tangentbindning för att åberopa detta menyalternativ. Till exempel väljer jag Cmd+Ctrl+S för att visa och dölja färginformation.

Detta är naturligtvis inte intuitivt jämfört med det ursprungliga insticksprogrammet, men det är bättre än ingenting.

Hur man felsöker Xcode-tillägg

Att arbeta med och felsöka tillägg är okomplicerat. Vi kan använda Xcode för att felsöka Xcode. Den felsökta versionen av Xcode har en grå ikon.

Hur man installerar Xcode-tillägg

Utvidgningen måste ha en tillhörande macOS-app. Denna kan distribueras till Mac Appstore eller vara självsignerad. Jag har skrivit en artikel om hur man gör detta.

Alla tillägg för en app måste uttryckligen aktiveras via ”Systeminställningar”.

Xcode-tillägget fungerar bara med editor för tillfället, så vi måste öppna en källfil för att Editor-menyn ska få effekt.

AppleScript i XcodeWay

I Xcode-tillägg fungerar inte längre NSWorkspace, NSTask och konstruktion av privata klasser. Eftersom jag har använt Finder Sync Extension i FinderGo tänkte jag att jag kunde prova samma AppleScript-skript för Xcode-tillägg.

AppleScript är ett skriptspråk som skapats av Apple. Det gör det möjligt för användare att direkt styra skriptbara Macintosh-program samt delar av själva macOS. Du kan skapa skript – uppsättningar av skriftliga instruktioner – för att automatisera repetitiva uppgifter, kombinera funktioner från flera skriptbara program och skapa komplexa arbetsflöden.

För att prova AppleScript kan du använda appen Script Editor som är inbyggd i macOS för att skriva prototypfunktioner. Funktionsdeklarationen börjar med on och slutar med end . För att undvika potentiella konflikter med systemfunktioner brukar jag använda my som prefix. Här är hur jag förlitar mig på System Events för att få fram hemkatalogen.

Terminologin för skript för användargränssnitt finns i ”Processes Suite” i skriptlexikonet ”System Events”. Denna svit innehåller terminologi för att interagera med de flesta typer av användargränssnittselement, inklusive:

  • fönster
  • knappar
  • kryssrutor
  • menyer
  • radioknappar
  • textfält.

I Systemhändelser representerar process-klassen en app som körs.

Många bra medborgarprogram stöder AppleScript genom att exponera vissa av sina funktioner, så att dessa kan användas av andra program. Så här hämtar jag den aktuella låten från Spotify i 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

För att få fram alla möjliga kommandon för en viss app kan vi öppna ordlistan i Script Editor. Där kan vi lära oss vilka funktioner och parametrar som stöds.

Om du tycker att Objective C är svårt är AppleScript mycket svårare. Syntaxen är spretig och felbenägen. Som referens finns här hela skriptfilen som driver XcodeWay.

För att öppna en viss mapp säger du till Finder med hjälp av POSIX file. Jag refaktoriserar varje funktion till funktion för bättre återanvändning av kod.

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

För att köra AppleScript inuti en macOS-app eller ett macOS-tillägg måste vi konstruera en AppleScript-deskriptor med rätt processserienummer och händelseidentifierare.

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}

Andra uppgifter, som att kontrollera den aktuella Git-fjärrkontrollen, är lite knepigare. Många gånger vill jag dela länken till den fil som jag felsöker med min fjärrteamkamrat, så att de vet vilken fil jag hänvisar till. Detta går att göra genom att använda shell script inuti 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

Vi kan använda quoted och strängkonkatenation för att bilda strängar. Lyckligtvis kan vi exponera Foundation ramverket och vissa klasser. Här är hur jag exponerar NSString för att dra nytta av alla befintliga funktioner. Att skriva stränghantering från grunden med vanlig AppleScript tar mycket tid.

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

Med detta kan vi bygga våra andra funktioner för stränghantering.

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

En häftig funktion som XcodeWay stöder är möjligheten att gå till dokumentkatalogen för den aktuella appen i simulatorn. Detta är praktiskt när vi behöver inspektera ett dokument för att kontrollera sparad eller cachad data. Katalogen är dynamisk så den är svår att upptäcka. Vi kan dock sortera katalogen efter den senast uppdaterade. Nedan visas hur vi kedjar flera shell scripts-kommandon för att hitta mappen.

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

Denna funktion hjälpte mig mycket när jag utvecklade Gallery för att kontrollera om videoklipp och nedladdade bilder sparas på rätt plats.

Ingen av skripten verkar dock fungera. Scripting har alltid varit en del av macOS sedan 1993. Men i och med tillkomsten av Mac Appstore och säkerhetsfrågor blev AppleScript slutligen begränsat i mitten av 2012. Det var då App Sandbox infördes.

App Sandbox

App Sandbox är en teknik för åtkomstkontroll i macOS som tillämpas på kärnnivå. Den är utformad för att begränsa skadorna på systemet och användarens data om en app äventyras. Appar som distribueras via Mac App Store måste anta App Sandbox.

För att ett Xcode-tillägg ska kunna laddas av Xcode måste det också ha stöd för App Sandbox.

I början av tillämpningen av App Sandbox kunde vi använda App Sandbox Temporary Exception (tillfälliga undantag i App Sandbox) för att tillfälligt ge vår app tillgång till Apple Script.

Detta är nu inte möjligt.

Enda sättet för AppleScript att köras är om det finns i mappen ~/Library/Application Scripts.

Hur man installerar anpassade skript

MacOS-appar eller tillägg kan inte bara installera skript i Application Scripts på egen hand. De behöver användarens samtycke.

Ett möjligt sätt att göra det är att aktivera Read/Write och visa en dialogruta med hjälp av NSOpenPanel för att be användaren att välja mapp för att installera våra skript.

För XcodeWay väljer jag att tillhandahålla ett skalskript för installation så att användaren har ett snabbt sätt att installera skript.

#!/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 är mycket kraftfullt. Allt detta görs explicit så att användaren har fullständig kontroll över vilka saker som kan göras.

Likt ett tillägg görs ett skript asynkront i en annan process med hjälp av XPC för kommunikation mellan processerna. Detta ökar säkerheten eftersom ett skript inte har tillgång till adressutrymmet för vår app eller vårt tillägg.

Mer säkerhet i macOS Mojave

I år, på WWDC 2018, introducerade Apple macOS Mojave som fokuserar på många säkerhetsförbättringar. I Dina appar och framtiden för macOS-säkerhet kan vi lära oss mer om nya säkerhetskrav för macOS-appar. Ett av dem är användningsbeskrivningen för AppleEvents.

unable to load info.plist exceptions (egpu overrides)

Vi brukade deklarera användningsbeskrivning för många behörigheter i iOS, som fotobibliotek, kamera och push-notiser. Nu måste vi deklarera användningsbeskrivningen för AppleEvents.

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

Första gången vårt tillägg försöker utföra några AppleScript-kommandon visas ovanstående dialogruta för att be om användarens samtycke. Användaren kan ge eller neka tillstånd, men för Xcode kan du säga ja?

Fixen för oss är att deklarera NSAppleEventsUsageDescription i vårt app-mål. Vi behöver bara deklarera i appmålet, inte i tilläggsmålet.

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

Var ska vi ta vägen härifrån

Huff huff, whew! Tack för att du följde med på en så lång resa. Att göra ramverk och verktyg tar mycket tid, särskilt plugins och tillägg – vi måste kontinuerligt ändra för att anpassa dem till nya operativsystem och säkerhetskrav. Men det är en givande process, eftersom vi har lärt oss mer och har några verktyg för att spara vår dyrbara tid.

För din referens, här är mina tillägg som är helt och hållet med öppen källkod.

  • XcodeWay
  • XcodeColorSense2