Articles

XPC-tjänster i macOS-appen

Lê Điền Phúc
Lê Điền Phúc

Follow

4 september, 2020 – 9 min read

För XPC brukade vi plocka upp Sockets och Mach Messages (Mach Ports).

XPC-mekanismen erbjuder ett alternativ till sockets (eller Mach Services som använder MIG) för IPC. Vi kan till exempel ha en process som fungerar som en ”server” som väntar på att klienter ska få tillgång till dess API och tillhandahålla någon tjänst.

XPC Services on applications

När vi talar om XPC Services (stort ”S”) hänvisar vi till det paket som kallas XPC Service. Bundles i Apples ekosystem hänvisar till enheter som representeras av en specifik katalogstruktur. De vanligaste Bundles du möter är Application Bundles. Om du högerklickar på ett program (till exempel Chess.app) och väljer Visa innehåll är det du hittar en katalogstruktur. Tillbaka till XPC: program kan ha flera XPC Service Bundles. Du hittar dem i katalogen Contents/XPCServices/ i programpaketet. Du kan söka i katalogen /Applications och se hur många av programmen som förlitar sig på XPC-tjänster.

Du kan också ha XPC-tjänster inuti ramverk (som är en annan typ av paket).

Tillkommande fördelar med XPC-tjänster

Användning av XPC-tjänster i våra program gör det möjligt att bryta upp en del funktionalitet i separata moduler (XPC-tjänsten). Vi kan skapa en XPC-tjänst som kan ansvara för att köra vissa kostsamma men sällan förekommande uppgifter. Till exempel en kryptouppgift för att generera slumptal.

En annan ytterligare fördel är att XPC-tjänsten körs i en egen process. Om den processen kraschar eller dödas påverkar det inte vårt huvudprogram. Föreställ dig att ditt program har stöd för användardefinierade plugins. Och plugins byggs med hjälp av XPC-tjänster. Om de är dåligt kodade och kraschar kommer de inte att påverka integriteten hos din huvudapplikation.

En ytterligare fördel med XPC-tjänsten är att de kan ha egna rättigheter. Programmet kommer endast att kräva rättigheten när det använder sig av en tjänst som tillhandahålls av XPC-tjänsten som kräver rättigheten. Tänk dig att du har en applikation som använder plats men bara för specifika funktioner. Du kan flytta dessa funktioner till en XPC-tjänst och lägga till lokaliseringsrättigheten endast för den XPC-tjänsten. Om din användare aldrig behöver funktionen som använder platsen kommer den inte att uppmanas att begära behörigheter, vilket gör användningen av din app mer pålitlig.

XPC och vår vän launchd

launchd är den första processen som körs på vårt system. Den ansvarar för att starta och hantera andra processer, tjänster och daemons. launchd ansvarar också för schemaläggning av uppgifter. Därför är det logiskt att launchd också ansvarar för hanteringen av XPC-tjänster.

XPC-tjänsten kan stoppas om den har varit inaktiv under en längre tid eller startas på begäran. All hantering sköts av launchd och vi behöver inte göra något för att det ska fungera.

Launchd har information om resurstillgänglighet och minnestryck i hela systemet, vem är bäst lämpad att fatta beslut om hur vi mest effektivt använder vårt systems resurser än launchd

Implementera XPC-tjänster

En XPC-tjänst är ett paket i katalogen Contents/XPCServices i huvudprogrampaketet; XPC-tjänstpaketet innehåller en Info.plist-fil, en körbar fil och eventuella resurser som behövs för tjänsten. XPC-tjänsten anger vilken funktion som ska anropas när tjänsten tar emot meddelanden genom att anropa xpc_main(3) Mac OS X Developer Tools Manual Page från sin huvudfunktion.

För att skapa en XPC-tjänst i Xcode gör du följande:

  1. Lägg till ett nytt mål till ditt projekt med hjälp av mallen XPC-tjänst.
  2. Lägg till en fas Kopiera filer till programmets bygginställningar, som kopierar XPC-tjänsten till mappen Contents/XPCServices i huvudprogrampaketet.
  3. Lägg till ett beroende till programmets bygginställningar, för att ange att den är beroende av XPC-tjänstpaketet.
  4. Om du skriver en XPC-tjänst på låg nivå (C-baserad), implementerar du en minimal huvudfunktion för att registrera din händelsehanterare, som visas i följande kodlista. Ersätt my_event_handler med namnet på din händelsehanteringsfunktion.
int main(int argc, const char *argv) {
xpc_main(my_event_handler);
// The xpc_main() function never returns.
exit(EXIT_FAILURE);
}

Om du skriver en tjänst på hög nivå (Objective-C-baserad) med hjälp av NSXPCConnection skapar du först en anslutningsdelegatklass som överensstämmer med protokollet NSXPCListenerDelegate. Implementera sedan en minimal huvudfunktion som skapar och konfigurerar ett lyssnarobjekt, enligt följande kodlistning.

int main(int argc, const char *argv) {
MyDelegateClass *myDelegate = ...
NSXPCListener *listener =
;
listener.delegate = myDelegate;
;
// The resume method never returns.
exit(EXIT_FAILURE);
}

Användning av tjänsten

Sättet du använder en XPC-tjänst beror på om du arbetar med C API (XPC Services) eller Objective-C API (NSXPCConnection).

Användning av API:et NSXPCConnection i Objective-C API:et NSXPCConnection i Objective-C tillhandahåller ett gränssnitt för fjärrproceduranrop på hög nivå som gör det möjligt att anropa metoder på objekt i en process från en annan process (vanligtvis ett program som anropar en metod i en XPC-tjänst). NSXPCConnection API serialiserar automatiskt datastrukturer och objekt för överföring och deserialiserar dem i andra änden. Som ett resultat av detta beter sig anrop av en metod på ett fjärrobjekt ungefär som att anropa en metod på ett lokalt objekt.

För att kunna använda API:et NSXPCConnection måste du skapa följande:

  • Ett gränssnitt. Detta består huvudsakligen av ett protokoll som beskriver vilka metoder som ska kunna anropas från fjärrprocessen. Detta beskrivs i Utforma ett gränssnitt
  • Ett anslutningsobjekt på båda sidor. På tjänstesidan beskrevs detta tidigare i Skapa tjänsten. På klientsidan beskrivs detta i Anslutning till och användning av ett gränssnitt.
  • En lyssnare. Den här koden i XPC-tjänsten tar emot anslutningar. Detta beskrivs i Acceptera en anslutning i hjälparen. Meddelanden.

Övergripande arkitektur

Övergripande arkitektur

När man arbetar med hjälpprogram som är baserade på NSXPCConnection, har både huvudapplikationen och hjälpprogrammet en instans av NSXPCConnection. Huvudprogrammet skapar själv sitt anslutningsobjekt, vilket gör att hjälpprogrammet startas. En delegatmetod i hjälpprogrammet får sitt anslutningsobjekt när anslutningen upprättas. Detta illustreras i figur 4-1.

Varje NSXPCConnection-objekt tillhandahåller tre viktiga funktioner:

  • En exportedInterface-egenskap som beskriver de metoder som ska göras tillgängliga för den motsatta sidan av anslutningen.
  • En exportedObject-egenskap som innehåller ett lokalt objekt för att hantera metodanrop som kommer från den andra sidan av anslutningen.
  • Förmågan att erhålla ett proxyobjekt för att anropa metoder på den andra sidan av anslutningen.

När huvudprogrammet anropar en metod på ett proxyobjekt anropar XPC-tjänstens NSXPCConnection-objekt den metoden på det objekt som lagras i dess exportedObject-egenskap.

Samma sak gäller om XPC-tjänsten erhåller ett proxyobjekt och anropar en metod på det objektet, så anropar huvudapplikationens NSXPCConnection-objekt den metoden på det objekt som är lagrat i dess exportedObject-egenskap

Utformning av ett gränssnitt

NASXPCConnection API drar nytta av Objective-C-protokoll för att definiera det programmatiska gränssnittet mellan den anropande applikationen och tjänsten. Alla instansmetoder som du vill anropa från den motsatta sidan av en anslutning måste uttryckligen definieras i ett formellt protokoll. Till exempel:

@protocol FeedMeACookie
- (void)feedMeACookie: (Cookie *)cookie;
@end

Då kommunikation över XPC är asynkron måste alla metoder i protokollet ha returtypen void. Om du behöver returnera data kan du definiera ett svarsblock så här:

@protocol FeedMeAWatermelon
- (void)feedMeAWatermelon: (Watermelon *)watermelon
reply:(void (^)(Rind *))reply;
@end

En metod kan bara ha ett svarsblock. Eftersom anslutningar är dubbelriktade kan XPC-tjänsthjälparen dock också svara genom att anropa metoder i det gränssnitt som tillhandahålls av huvudprogrammet, om så önskas.

Varje metod måste ha returtypen void, och alla parametrar till metoder eller svarsblock måste vara antingen:

  • Aritmetiska typer (int, char, float, double, uint64_t, NSUInteger och så vidare)
  • BOOL
  • C strings
  • C strukturer och matriser som endast innehåller de typer som anges ovan
  • Objective-C-objekt som implementerar protokollet NSSecureCoding.

Viktigt: Om en metod (eller dess svarsblock) har parametrar som är Objective-C-samlingsklasser (NSDictionary, NSArray och så vidare), och om du behöver passera dina egna anpassade objekt inom en samling, måste du uttryckligen tala om för XPC att tillåta den klassen som medlem i den samlingsparametern.

Anslutning till och användning av ett gränssnitt

När du har definierat protokollet måste du skapa ett gränssnittsobjekt som beskriver det. Detta gör du genom att anropa metoden interfaceWithProtocol: i klassen NSXPCInterface. Till exempel:

NSXPCInterface *myCookieInterface =
;

När du har skapat gränssnittsobjektet måste du i huvudprogrammet konfigurera en anslutning med det genom att anropa metoden initWithServiceName:. Till exempel:

NSXPCConnection *myConnection = 
initWithServiceName:@"com.example.monster"];
myConnection.remoteObjectInterface = myCookieInterface;
;

Notera: För att kommunicera med XPC-tjänster utanför ditt app-paket kan du också konfigurera en XPC-anslutning med metoden initWithMachServiceName:.

Här kan huvudprogrammet anropa metoderna remoteObjectProxy eller remoteObjectProxyWithErrorHandler: på myConnection-objektet för att erhålla ett proxyobjekt.

Detta objekt fungerar som en proxy för det objekt som XPC-tjänsten har ställt in som sitt exporterade objekt (genom att ställa in egenskapen exportedObject). Objektet måste följa det protokoll som definieras av egenskapen remoteObjectInterface.

När ditt program anropar en metod på proxyobjektet anropas motsvarande metod på det exporterade objektet i XPC-tjänsten. När tjänstens metod anropar svarsblocket serialiseras parametervärdena och skickas tillbaka till programmet, där parametervärdena deserialiseras och skickas till svarsblocket. (Svarsblocket exekveras inom programmets adressutrymme.)

Anmärkning: Om du vill tillåta hjälpprocessen att anropa metoder på ett objekt i ditt program måste du ställa in egenskaperna exportedInterface och exportedObject innan du anropar resume. Dessa egenskaper beskrivs närmare i nästa avsnitt.

Accepting a Connection in the Helper

När en NSXPCConnection-baserad hjälpprocess tar emot det första meddelandet från en anslutning anropas listener-delegatens listener:shouldAcceptNewConnection: metod med ett lyssnarobjekt och ett anslutningsobjekt. Med den här metoden kan du bestämma om du ska acceptera anslutningen eller inte; den bör returnera YES för att acceptera anslutningen eller NO för att neka anslutningen.

Anmärkning: Hjälparen tar emot en anslutningsbegäran när det första faktiska meddelandet skickas. Anslutningsobjektets återupptagningsmetod orsakar inte att ett meddelande skickas.

Förutom att fatta principbeslut måste den här metoden konfigurera anslutningsobjektet. Om hjälparen beslutar sig för att acceptera anslutningen måste den särskilt ange följande egenskaper för anslutningen:

  • exportedInterface – ett gränssnittsobjekt som beskriver protokollet för det objekt som du vill exportera. (Skapandet av det här objektet beskrevs tidigare i Anslutning till och användning av ett gränssnitt.)
  • exportedObject – det lokala objektet (vanligtvis i hjälparen) till vilket fjärrklientens metodanrop ska levereras. När den motsatta änden av anslutningen (vanligtvis i programmet) anropar en metod på anslutningens proxyobjekt anropas motsvarande metod på det objekt som anges av egenskapen exportedObject.

När dessa egenskaper har ställts in bör den anropa anslutningsobjektets resume-metod innan den returnerar YES. Även om delegaten kan skjuta upp anropet av resume kommer anslutningen inte att ta emot några meddelanden förrän den gör det.

Sändning av meddelanden

Sändning av meddelanden med NSXPC är lika enkelt som att göra ett metodanrop. Med gränssnittet myCookieInterface (beskrivet i tidigare avsnitt) på XPC-anslutningsobjektet myConnection kan du till exempel anropa feedMeACookie-metoden så här:

Cookie *myCookie = ...
feedMeACookie: myCookie];

När du anropar den metoden anropas motsvarande metod i XPC-hjälpen automatiskt. Den metoden kan i sin tur använda XPC-hjälparens anslutningsobjekt på liknande sätt för att anropa en metod på objektet som exporteras av huvudapplikationen.

Hantering av fel

Förutom eventuella felhanteringsmetoder som är specifika för en viss hjälpares uppgift bör både XPC-tjänsten och huvudapplikationen också tillhandahålla följande XPC-felhanteringsblock:

  • Avbrottshanterare – anropas när processen i andra änden av anslutningen har kraschat eller på annat sätt stängt sin anslutning. Det lokala anslutningsobjektet är vanligtvis fortfarande giltigt – varje framtida anrop kommer automatiskt att skapa en ny hjälparbetarinstans om det inte är omöjligt att göra det – men du kan behöva återställa alla tillstånd som hjälparen annars skulle ha behållit.

Hanteraren åberopas i samma kö som svarsmeddelanden och andra hanterare, och den exekveras alltid efter alla andra meddelanden eller hanterare av svarsblock (med undantag för invaliditetshanteraren). Det är säkert att göra nya förfrågningar på anslutningen från en avbrottshanterare.

  • Invalidationshanterare – anropas när invalidiseringsmetoden anropas eller när en XPC-hjälpare inte kunde startas. När denna handläggare anropas är det lokala anslutningsobjektet inte längre giltigt och måste återskapas. Detta är alltid den sista handläggaren som kallas för ett anslutningsobjekt. När det här blocket anropas har anslutningsobjektet rivits ned. Det är inte möjligt att skicka ytterligare meddelanden på anslutningen vid den tidpunkten, vare sig i hanteraren eller någon annanstans i din kod.

I båda fallen bör du använda variabler i block för att tillhandahålla tillräckligt med kontextuell information – kanske en kö för pågående operationer och själva anslutningsobjektet – så att din hanterarkod kan göra något förnuftigt, t.ex. att försöka igen pågående operationer, riva ner anslutningen, visa en feldialog eller vad som helst annat som är meningsfullt i just din app.