Articles

Servicii XPC pe aplicația macOS

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

Follow
4 sept, 2020 – 9 min read

Înainte de XPC obișnuiam să luăm Sockets și Mach Messages (Mach Ports).

Mecanismul XPC oferă o alternativă la socket-uri (sau servicii Mach folosind MIG) pentru IPC. Am putea avea, de exemplu, un proces care acționează ca un „server” care așteaptă ca clienții să îi acceseze API-ul și să furnizeze un anumit serviciu.

Servicii XPC pe aplicații

Când vorbim despre Servicii XPC (cu „S” mare), ne referim la pachetul numit XPC Service. Bundle-urile din ecosistemul Apple se referă la entități reprezentate de o anumită structură de directoare. Cele mai frecvente pachete pe care le întâlniți sunt pachetele de aplicații. Dacă faceți clic dreapta pe orice aplicație (De exemplu, Chess.app) și selectați Show content (Afișare conținut), ceea ce veți găsi este o structură de directoare. Revenind la XPC, aplicațiile pot avea mai multe pachete de servicii XPC. Le veți găsi în directorul Contents/XPCServices/ din cadrul pachetului de aplicații. Puteți căuta în directorul /Applications și să vedeți câte dintre aplicații se bazează pe XPC Services.

De asemenea, puteți avea XPC Services în interiorul Frameworks (care sunt un alt tip de Bundle).

Beneficii suplimentare ale XPC Services

Utilizarea XPC Services în aplicațiile noastre ne permite să rupem anumite funcționalități în module separate (The XPC Service). Am putea crea un Serviciu XPC care se poate ocupa de rularea unor sarcini costisitoare, dar puțin frecvente. De exemplu, unele sarcini criptografice pentru a genera numere aleatoare.

Un alt beneficiu suplimentar este că Serviciul XPC rulează pe propriul proces. Dacă acel proces se blochează sau este ucis, nu afectează aplicația noastră principală. Imaginați-vă că aplicația dvs. suportă plugin-uri definite de utilizator. Iar pluginurile sunt construite cu ajutorul serviciilor XPC. Dacă acestea sunt prost codate și se prăbușesc, nu vor afecta integritatea aplicației dvs. principale.

Un beneficiu suplimentar al Serviciului XPC este că acestea pot avea propriile drepturi. Aplicația va avea nevoie de entitlement doar atunci când utilizează un serviciu furnizat de XPC Service care necesită acest entitlement. Imaginați-vă că aveți o aplicație care utilizează localizarea, dar numai pentru anumite funcții. Ați putea muta aceste caracteristici într-un serviciu XPC și ați putea adăuga dreptul de localizare numai la acel serviciu XPC. Dacă utilizatorul dvs. nu are niciodată nevoie de caracteristica care utilizează locația, nu i se vor solicita permisiuni, ceea ce face ca utilizarea aplicației dvs. să fie mai de încredere.

XPC și prietenul nostru launchd

launchd este primul proces care rulează pe sistemul nostru. Se ocupă de lansarea și gestionarea altor procese, servicii și demoni. launchd se ocupă, de asemenea, de programarea sarcinilor. Așa că este logic ca launchd să fie responsabil și de gestionarea serviciilor XPC.

Serviciul XPC poate fi oprit dacă a fost inactiv pentru o perioadă lungă de timp sau poate fi generat la cerere. Toată gestionarea este făcută de launchd, iar noi nu trebuie să facem nimic pentru ca acesta să funcționeze.

launchd are informații despre disponibilitatea resurselor la nivelul întregului sistem și despre presiunea asupra memoriei, cine mai bine decât launchd poate lua decizii cu privire la modul cel mai eficient de utilizare a resurselor sistemului nostru

Implementarea serviciilor XPC

Un serviciu XPC este un pachet în directorul Contents/XPCServices al pachetului principal de aplicații; pachetul de servicii XPC conține un fișier Info.plist, un executabil și orice resurse necesare serviciului. Serviciul XPC indică ce funcție să apeleze atunci când serviciul primește mesaje prin apelarea xpc_main(3) Mac OS X Developer Tools Manual Page din funcția sa principală.

Pentru a crea un serviciu XPC în Xcode, efectuați următoarele:

  1. Adaugați o nouă țintă la proiectul dumneavoastră, utilizând șablonul XPC Service.
  2. Adaugați o fază de copiere a fișierelor la setările de construcție ale aplicației dumneavoastră, care copiază serviciul XPC în directorul Contents/XPCServices al pachetului principal al aplicației.
  3. Adaugați o dependență la setările de construcție ale aplicației dumneavoastră, pentru a indica faptul că aceasta depinde de pachetul de servicii XPC.
  4. Dacă scrieți un serviciu XPC de nivel scăzut (bazat pe C), implementați o funcție principală minimă pentru a vă înregistra gestionarul de evenimente, așa cum se arată în următoarea listă de cod. Înlocuiți my_event_handler cu numele funcției dvs. de gestionare a evenimentelor.
int main(int argc, const char *argv) {
xpc_main(my_event_handler);
// The xpc_main() function never returns.
exit(EXIT_FAILURE);
}

Dacă scrieți un serviciu de nivel înalt (bazat pe Objective-C) folosind NSXPCConnection, creați mai întâi o clasă delegată de conexiune care să fie conformă cu protocolul NSXPCListenerDelegate. Apoi, implementați o funcție principală minimă care creează și configurează un obiect ascultător, așa cum se arată în următoarea listă de cod.

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

Utilizarea serviciului

Modul în care utilizați un serviciu XPC depinde de faptul că lucrați cu API C (XPC Services) sau cu API Objective-C (NSXPCConnection).

Utilizarea API Objective-C NSXPCConnection API-ul Objective-C NSXPCConnection oferă o interfață de nivel înalt de apelare a procedurilor la distanță care vă permite să apelați metode asupra obiectelor dintr-un proces de la un alt proces (de obicei o aplicație care apelează o metodă dintr-un serviciu XPC). API NSXPCConnection serializează automat structurile de date și obiectele pentru transmisie și le deserializează la celălalt capăt. Ca urmare, apelarea unei metode pe un obiect la distanță se comportă la fel ca și apelarea unei metode pe un obiect local.

Pentru a utiliza API-ul NSXPCConnection, trebuie să creați următoarele:

  • O interfață. Aceasta constă în principal într-un protocol care descrie ce metode ar trebui să poată fi apelate din procesul la distanță. Acest lucru este descris în Proiectarea unei interfețe
  • Un obiect de conexiune de ambele părți. Pe partea serviciului, acesta a fost descris anterior în Crearea serviciului. Pe partea clientului, acest lucru este descris în Conectarea la și utilizarea unei interfețe.
  • Un ascultător. Acest cod din serviciul XPC acceptă conexiuni. Acest lucru este descris în Acceptarea unei conexiuni în Helper. Messages.

Arhitectura generală

Arhitectura generală

Când se lucrează cu aplicații ajutătoare bazate pe NSXPCConnection, atât aplicația principală, cât și cea ajutătoare au o instanță de NSXPCConnection. Aplicația principală își creează singură obiectul de conexiune, ceea ce determină lansarea helper-ului. O metodă delegată din aplicația ajutătoare primește obiectul său de conexiune atunci când se stabilește conexiunea. Acest lucru este ilustrat în figura 4-1.

Care obiect NSXPCConnection oferă trei caracteristici cheie:

  • O proprietate exportedInterface care descrie metodele care ar trebui să fie puse la dispoziția părții opuse a conexiunii.
  • O proprietate exportedObject care conține un obiect local pentru a gestiona apelurile de metode care vin de pe cealaltă parte a conexiunii.
  • Capacitatea de a obține un obiect proxy pentru a apela metodele de pe cealaltă parte a conexiunii.

Când aplicația principală apelează o metodă pe un obiect proxy, obiectul NSXPCConnection al serviciului XPC apelează acea metodă pe obiectul stocat în proprietatea sa exportedObject.

În mod similar, în cazul în care serviciul XPC obține un obiect proxy și apelează o metodă pe acel obiect, obiectul NSXPCConnection al aplicației principale apelează acea metodă pe obiectul stocat în proprietatea sa exportedObject

Desenarea unei interfețe

API-ul NSXPCConnection profită de protocoalele Objective-C pentru a defini interfața programatică dintre aplicația apelantă și serviciu. Orice metodă de instanță pe care doriți să o apelați din partea opusă a unei conexiuni trebuie să fie definită în mod explicit într-un protocol formal. De exemplu:

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

Pentru că comunicarea prin XPC este asincronă, toate metodele din protocol trebuie să aibă un tip de returnare void. Dacă aveți nevoie să returnați date, puteți defini un bloc de răspuns astfel:

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

O metodă poate avea un singur bloc de răspuns. Cu toate acestea, deoarece conexiunile sunt bidirecționale, asistentul de serviciu XPC poate răspunde, de asemenea, prin apelarea metodelor din interfața furnizată de aplicația principală, dacă se dorește.

Care metodă trebuie să aibă un tip de returnare void, iar toți parametrii metodelor sau blocurilor de răspuns trebuie să fie fie:

  • Tipuri aritmetice (int, char, float, double, uint64_t, NSUInteger și așa mai departe)
  • BOOL
  • C șiruri de caractere
  • C structuri și array-uri care conțin numai tipurile enumerate mai sus
  • Obiecte Objective-C care implementează protocolul NSSecureCoding.

Important: Dacă o metodă (sau blocul său de răspuns) are parametri care sunt clase de colecție Objective-C (NSDictionary, NSArray și așa mai departe) și dacă aveți nevoie să treceți propriile obiecte personalizate în cadrul unei colecții, trebuie să îi spuneți în mod explicit lui XPC să permită acea clasă ca membru al acelui parametru de colecție.

Conectarea și utilizarea unei interfețe

După ce ați definit protocolul, trebuie să creați un obiect de interfață care să îl descrie. Pentru a face acest lucru, apelați metoda interfaceWithProtocol: a clasei NSXPCInterface. De exemplu:

NSXPCInterface *myCookieInterface =
;

După ce ați creat obiectul de interfață, în cadrul aplicației principale, trebuie să configurați o conexiune cu acesta prin apelarea metodei initWithServiceName:. De exemplu:

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

Nota: Pentru a comunica cu serviciile XPC din afara pachetului de aplicații, puteți configura, de asemenea, o conexiune XPC cu metoda initWithMachServiceName:.

În acest moment, aplicația principală poate apela metodele remoteObjectProxy sau remoteObjectProxyWithErrorHandler: asupra obiectului myConnection pentru a obține un obiect proxy.

Acest obiect acționează ca un proxy pentru obiectul pe care serviciul XPC l-a setat ca obiect exportat (prin setarea proprietății exportedObject). Acest obiect trebuie să fie în conformitate cu protocolul definit de proprietatea remoteObjectInterface.

Când aplicația dumneavoastră apelează o metodă pe obiectul proxy, metoda corespunzătoare este apelată pe obiectul exportat din cadrul serviciului XPC. Atunci când metoda serviciului apelează blocul de răspuns, valorile parametrilor sunt serializate și trimise înapoi la aplicație, unde valorile parametrilor sunt deserializate și trecute la blocul de răspuns. (Blocul de răspuns se execută în interiorul spațiului de adrese al aplicației.)

Nota: Dacă doriți să permiteți procesului auxiliar să apeleze metode pe un obiect din aplicația dumneavoastră, trebuie să setați proprietățile exportedInterface și exportedObject înainte de a apela resume. Aceste proprietăți sunt descrise în continuare în secțiunea următoare.

Acceptarea unei conexiuni în Helper

Când un helper bazat pe NSXPCConnection primește primul mesaj de la o conexiune, metoda listener:shouldAcceptNewConnection: a delegatului de ascultare este apelată cu un obiect de ascultare și un obiect de conexiune. Această metodă vă permite să decideți dacă să acceptați sau nu conexiunea; ar trebui să returneze YES pentru a accepta conexiunea sau NO pentru a refuza conexiunea.

Nota: Ajutorul primește o cerere de conexiune atunci când este trimis primul mesaj real. Metoda de reluare a obiectului de conexiune nu determină trimiterea unui mesaj.

În plus față de luarea deciziilor de politică, această metodă trebuie să configureze obiectul de conexiune. În special, presupunând că asistentul decide să accepte conexiunea, acesta trebuie să seteze următoarele proprietăți ale conexiunii:

  • exportedInterface – un obiect de interfață care descrie protocolul pentru obiectul pe care doriți să îl exportați. (Crearea acestui obiect a fost descrisă anterior în Conectarea la și utilizarea unei interfețe.)
  • exportedObject – obiectul local (de obicei în helper) la care ar trebui să fie livrate apelurile de metodă ale clientului la distanță. Ori de câte ori capătul opus al conexiunii (de obicei în aplicație) apelează o metodă pe obiectul proxy al conexiunii, metoda corespunzătoare este apelată pe obiectul specificat de proprietatea exportedObject.

După ce setează aceste proprietăți, ar trebui să apeleze metoda resume a obiectului de conexiune înainte de a returna YES. Deși delegatul poate amâna apelarea reluării, conexiunea nu va primi niciun mesaj până când nu face acest lucru.

Întoarcerea mesajelor

Întoarcerea mesajelor cu NSXPC este la fel de simplă ca efectuarea unui apel la o metodă. De exemplu, având în vedere interfața myCookieInterface (descrisă în secțiunile anterioare) pe obiectul de conexiune XPC myConnection, puteți apela metoda feedMeACookie astfel:

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

Când apelați această metodă, metoda corespunzătoare din helperul XPC este apelată automat. Acea metodă, la rândul său, ar putea folosi în mod similar obiectul conexiune al ajutorului XPC pentru a apela o metodă a obiectului exportat de aplicația principală.

Manipularea erorilor

În plus față de orice metode de manipulare a erorilor specifice sarcinii unui anumit ajutor, atât serviciul XPC, cât și aplicația principală ar trebui să furnizeze, de asemenea, următoarele blocuri de manipulare a erorilor XPC:

  • Manipulator de întreruperi – apelat atunci când procesul de la celălalt capăt al conexiunii s-a prăbușit sau și-a închis conexiunea în alt mod. Obiectul conexiunii locale este de obicei încă valabil – orice apel viitor va genera automat o nouă instanță de ajutor, cu excepția cazului în care este imposibil de făcut acest lucru – dar este posibil să fie nevoie să resetați orice stare pe care ajutorul ar fi păstrat-o altfel.

Managerul este invocat pe aceeași coadă ca și mesajele de răspuns și alți manageri și este întotdeauna executat după orice alte mesaje sau manageri de blocuri de răspuns (cu excepția managerului de invalidare). Este sigur să se facă cereri noi pe conexiune de la un handler de întrerupere.

  • Managerul de invalidare – apelat atunci când este apelată metoda de invalidare sau când un ajutor XPC nu a putut fi inițiat. Atunci când acest handler este apelat, obiectul conexiunii locale nu mai este valabil și trebuie să fie recreat. Acesta este întotdeauna ultimul handler apelat pentru un obiect de conexiune. Atunci când acest bloc este apelat, obiectul de conexiune a fost distrus. Nu mai este posibil să trimiteți alte mesaje pe conexiune în acel moment, fie în interiorul gestionarului, fie în altă parte a codului dumneavoastră.

În ambele cazuri, ar trebui să utilizați variabile cu acoperire de bloc pentru a furniza suficiente informații contextuale – poate o coadă de operații în așteptare și obiectul de conexiune în sine – astfel încât codul gestionarului să poată face ceva sensibil, cum ar fi reluarea operațiilor în așteptare, distrugerea conexiunii, afișarea unui dialog de eroare sau orice alte acțiuni care au sens în aplicația dumneavoastră particulară.