Articles

Usługi XPC w aplikacji macOS

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

Follow

Sep 4, 2020 – 9 min read

Przed XPC dłubaliśmy w Socketach i Mach Messages (Mach Ports).

Mechanizm XPC oferuje alternatywę dla Socketów (lub Mach Services używających MIG) dla IPC. Możemy mieć na przykład proces, który działa jako „serwer” oczekujący na klientów, którzy uzyskają dostęp do jego API i dostarczą jakąś usługę.

Usługi XPC w aplikacjach

Gdy mówimy o Usługach XPC (duże 'S’), odnosimy się do pakietu o nazwie Usługa XPC. Wiązki w ekosystemie Apple odnosi się do podmiotów reprezentowanych przez określoną strukturę katalogów. Najczęstszym Bundle można spotkać są Application Bundles. Jeśli klikniesz prawym przyciskiem myszy na dowolnej aplikacji (na przykład Chess.app) i wybierzesz Pokaż zawartość, co znajdziesz jest struktura katalogów. Wracając do XPC, aplikacje mogą mieć wiele pakietów usług XPC. Znajdziesz je w katalogu Contents/XPCServices/ wewnątrz pakietu aplikacji. Yo może przeszukać twój katalog /Applications i zobaczyć jak wiele aplikacji polega na Usługach XPC.

Możesz również mieć Usługi XPC wewnątrz Struktur Ramowych (Które są innym typem Wiązek).

Dodatkowe Korzyści z Usług XPC

Używanie Usług XPC w naszych aplikacjach pozwala nam rozbić niektóre funkcjonalności w oddzielnych modułach (Usługa XPC). Możemy stworzyć usługę XPC, która może być odpowiedzialna za wykonywanie pewnych kosztownych, ale rzadkich zadań. Na przykład, jakieś zadanie kryptograficzne do generowania liczb losowych.

Kolejną dodatkową korzyścią jest to, że usługa XPC działa na swoim własnym procesie. Jeśli ten proces się zawiesi lub zostanie zabity, nie ma to wpływu na naszą główną aplikację. Wyobraź sobie, że Twoja aplikacja obsługuje zdefiniowane przez użytkownika wtyczki. I te wtyczki są zbudowane przy użyciu Usług XPC. Jeśli są źle zakodowane i ulegną awarii, nie wpłyną na integralność twojej głównej aplikacji.

Dodatkową zaletą Usług XPC jest to, że mogą one posiadać własne uprawnienia. Aplikacja będzie wymagać uprawnienia tylko wtedy, gdy korzysta z usługi świadczonej przez XPC Service, która wymaga uprawnienia. Wyobraź sobie, że masz aplikację, która używa lokalizacji, ale tylko dla określonych funkcji. Mógłbyś przenieść te funkcje do Usługi XPC i dodać uprawnienie lokalizacji tylko do tej Usługi XPC. Jeśli twój użytkownik nigdy nie potrzebuje funkcji, która używa lokalizacji, nie zostanie poproszony o uprawnienia, dzięki czemu korzystanie z twojej aplikacji będzie bardziej godne zaufania.

XPC i nasz przyjaciel launchd

launchd jest pierwszym procesem uruchamianym w naszym systemie. Odpowiada on za uruchamianie i zarządzanie innymi procesami, usługami i demonami. launchd jest również odpowiedzialny za planowanie zadań. Więc to ma sens, że launchd będzie również odpowiedzialny za zarządzanie usługami XPC.

Usługi XPC mogą być zatrzymane, jeśli były bezczynne przez długi czas, lub mogą być tworzone na żądanie. Całe zarządzanie jest wykonywane przez launchd, a my nie musimy nic robić, aby to działało.

launchd ma informacje o dostępności zasobów w całym systemie i nacisku na pamięć, kto najlepiej podejmie decyzje, jak najefektywniej wykorzystać zasoby naszego systemu niż launchd

Wdrażanie usług XPC

Usługa XPC jest pakietem w katalogu Contents/XPCServices głównego pakietu aplikacji; pakiet usługi XPC zawiera plik Info.plist, plik wykonywalny i wszelkie zasoby potrzebne usłudze. Usługa XPC wskazuje, którą funkcję należy wywołać, gdy usługa otrzymuje wiadomości, wywołując xpc_main(3) Mac OS X Developer Tools Manual Page ze swojej funkcji głównej.

Aby utworzyć usługę XPC w Xcode, wykonaj następujące czynności:

  1. Dodaj nowy cel do swojego projektu, używając szablonu Usługi XPC.
  2. Dodaj fazę Kopiuj pliki do ustawień budowania aplikacji, która kopiuje usługę XPC do katalogu Contents/XPCServices głównego pakietu aplikacji.
  3. Dodaj zależność do ustawień budowania aplikacji, aby wskazać, że zależy ona od pakietu usług XPC.
  4. Jeśli piszesz niskopoziomową (opartą na języku C) usługę XPC, zaimplementuj minimalną funkcję główną, aby zarejestrować obsługę zdarzeń, jak pokazano w poniższym listingu kodu. Zastąp my_event_handler nazwą swojej funkcji obsługi zdarzeń.
int main(int argc, const char *argv) {
xpc_main(my_event_handler);
// The xpc_main() function never returns.
exit(EXIT_FAILURE);
}

Jeśli piszesz wysokopoziomową (opartą na Objective-C) usługę używającą NSXPCConnection, najpierw stwórz klasę delegata połączenia, która jest zgodna z protokołem NSXPCListenerDelegate. Następnie zaimplementuj minimalną główną funkcję, która tworzy i konfiguruje obiekt słuchacza, jak pokazano na poniższym listingu kodu.

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

Używanie usługi

Sposób, w jaki używasz usługi XPC zależy od tego, czy pracujesz z C API (Usługi XPC) lub Objective-C API (NSXPCConnection).

Używanie API Objective-C NSXPCConnection API Objective-C NSXPCConnection zapewnia wysokopoziomowy interfejs zdalnego wywoływania procedur, który umożliwia wywoływanie metod na obiektach w jednym procesie z innego procesu (zwykle aplikacji wywołującej metodę w usłudze XPC). API NSXPCConnection automatycznie serializuje struktury danych i obiekty do transmisji i deserializuje je na drugim końcu. W rezultacie, wywołanie metody na zdalnym obiekcie zachowuje się podobnie jak wywołanie metody na lokalnym obiekcie.

Aby używać NSXPCConnection API, musisz utworzyć następujące elementy:

  • Interfejs. Składa się on głównie z protokołu, który opisuje jakie metody powinny być wywoływane ze zdalnego procesu. Zostało to opisane w rozdziale Projektowanie interfejsu
  • Obiekt połączenia po obu stronach. Po stronie usługi zostało to opisane wcześniej w Tworzenie usługi. Po stronie klienta jest to opisane w Connecting to and Using an Interface.
  • Słuchacz. Ten kod w usłudze XPC akceptuje połączenia. Jest to opisane w Akceptowanie połączenia w helperze. Messages.

Overall Architecture

Overal architecture

Podczas pracy z aplikacjami pomocniczymi opartymi na NSXPCConnection, zarówno główna aplikacja jak i aplikacja pomocnicza posiadają instancję NSXPCConnection. Główna aplikacja sama tworzy swój obiekt połączenia, co powoduje uruchomienie helpera. Metoda delegata w helperze otrzymuje swój obiekt połączenia, gdy połączenie jest ustanawiane. Zilustrowano to na rysunku 4-1.

Każdy obiekt NSXPCConnection zapewnia trzy kluczowe cechy:

  • Właściwość exportedInterface, która opisuje metody, które powinny być udostępnione przeciwnej stronie połączenia.
  • Właściwość exportedObject, która zawiera lokalny obiekt do obsługi wywołań metod przychodzących z drugiej strony połączenia.
  • Możliwość uzyskania obiektu proxy do wywoływania metod po drugiej stronie połączenia.

Gdy główna aplikacja wywołuje metodę na obiekcie proxy, obiekt NSXPCConnection usługi XPC wywołuje tę metodę na obiekcie przechowywanym w jego właściwości exportedObject.

Podobnie, jeśli usługa XPC uzyskuje obiekt proxy i wywołuje metodę na tym obiekcie, obiekt NSXPCConnection głównej aplikacji wywołuje tę metodę na obiekcie przechowywanym w jego właściwości exportedObject

Projektowanie interfejsu

Interfejs API NSXPCConnection wykorzystuje protokoły Objective-C do zdefiniowania programowego interfejsu między aplikacją wywołującą a usługą. Każda metoda instancji, którą chcesz wywołać z przeciwnej strony połączenia, musi być jawnie zdefiniowana w formalnym protokole. Na przykład:

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

Ponieważ komunikacja przez XPC jest asynchroniczna, wszystkie metody w protokole muszą mieć typ powrotu void. Jeśli potrzebujesz zwrócić dane, możesz zdefiniować blok odpowiedzi w ten sposób:

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

Metoda może mieć tylko jeden blok odpowiedzi. Ponieważ jednak połączenia są dwukierunkowe, pomocnik usługi XPC może również odpowiadać przez wywołanie metod w interfejsie dostarczonym przez główną aplikację, jeśli jest to pożądane.

Każda metoda musi mieć typ powrotu void, a wszystkie parametry do metod lub bloków odpowiedzi muszą być albo:

  • Typami arytmetycznymi (int, char, float, double, uint64_t, NSUInteger i tak dalej)
  • BOOL
  • Ciągami
  • C strukturami i tablicami zawierającymi tylko typy wymienione powyżej
  • Obiektami Objective-C, które implementują protokół NSSecureCoding.

Important: Jeśli metoda (lub jej blok odpowiedzi) ma parametry, które są klasami kolekcji Objective-C (NSDictionary, NSArray, i tak dalej), i jeśli potrzebujesz przekazać własne niestandardowe obiekty w ramach kolekcji, musisz wyraźnie powiedzieć XPC, aby zezwolić tej klasie jako członkowi tego parametru kolekcji.

Podłączanie do i używanie interfejsu

Po zdefiniowaniu protokołu, musisz utworzyć obiekt interfejsu, który go opisuje. Aby to zrobić, wywołaj metodę interfaceWithProtocol: na klasie NSXPCInterface. Na przykład:

NSXPCInterface *myCookieInterface =
;

Po utworzeniu obiektu interfejsu, w głównej aplikacji, musisz skonfigurować połączenie z nim poprzez wywołanie metody initWithServiceName:. Na przykład:

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

Uwaga: Dla komunikacji z usługami XPC poza twoim pakietem aplikacji, możesz również skonfigurować połączenie XPC za pomocą metody initWithMachServiceName:.

W tym momencie, główna aplikacja może wywołać metody remoteObjectProxy lub remoteObjectProxyWithErrorHandler: na obiekcie myConnection w celu uzyskania obiektu proxy.

Ten obiekt działa jako proxy dla obiektu, który usługa XPC ustawiła jako swój eksportowany obiekt (przez ustawienie właściwości exportedObject). Ten obiekt musi być zgodny z protokołem określonym przez właściwość remoteObjectInterface.

Gdy Twoja aplikacja wywołuje metodę na obiekcie proxy, odpowiednia metoda jest wywoływana na wyeksportowanym obiekcie wewnątrz usługi XPC. Kiedy metoda usługi wywołuje blok odpowiedzi, wartości parametrów są serializowane i wysyłane z powrotem do aplikacji, gdzie wartości parametrów są deserializowane i przekazywane do bloku odpowiedzi. (Blok odpowiedzi wykonuje się w przestrzeni adresowej aplikacji.)

Uwaga: Jeżeli chcesz pozwolić procesowi pomocniczemu wywoływać metody na obiekcie w twojej aplikacji, musisz ustawić właściwości exportedInterface i exportedObject przed wywołaniem resume. Właściwości te są opisane w następnej sekcji.

Przyjmowanie połączenia w helperze

Gdy helper oparty na NSXPCConnection otrzymuje pierwszą wiadomość od połączenia, wywoływana jest metoda listener:shouldAcceptNewConnection: delegata listenera z obiektem listenera i obiektem połączenia. Ta metoda pozwala zdecydować, czy zaakceptować połączenie, czy nie; powinna zwrócić YES, aby zaakceptować połączenie lub NO, aby odrzucić połączenie.

Uwaga: Pomocnik otrzymuje żądanie połączenia, gdy wysyłana jest pierwsza rzeczywista wiadomość. Metoda wznawiania obiektu połączenia nie powoduje wysłania wiadomości.

Oprócz podejmowania decyzji dotyczących polityki, metoda ta musi skonfigurować obiekt połączenia. W szczególności, zakładając, że helper zdecyduje się zaakceptować połączenie, musi ustawić następujące właściwości na połączeniu:

  • exportedInterface – obiekt interfejsu, który opisuje protokół dla obiektu, który chcesz wyeksportować. (Wytworzenie tego obiektu zostało opisane wcześniej w Podłączenie do i wykorzystanie interfejsu.)
  • exportedObject – obiekt lokalny (zazwyczaj w helperze), do którego mają być przekazywane wywołania metod klienta zdalnego. Ilekroć przeciwny koniec połączenia (zwykle w aplikacji) wywołuje metodę na obiekcie proxy połączenia, odpowiednia metoda jest wywoływana na obiekcie określonym przez właściwość exportedObject.

Po ustawieniu tych właściwości, powinien wywołać metodę resume obiektu połączenia przed zwróceniem YES. Chociaż delegat może odroczyć wywołanie wznowienia, połączenie nie otrzyma żadnych wiadomości dopóki tego nie zrobi.

Wysyłanie wiadomości

Wysyłanie wiadomości za pomocą NSXPC jest tak proste jak wywołanie metody. Na przykład, biorąc pod uwagę interfejs myCookieInterface (opisany w poprzednich sekcjach) na obiekcie połączenia XPC myConnection, możesz wywołać metodę feedMeACookie w następujący sposób:

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

Gdy wywołasz tę metodę, odpowiednia metoda w helperze XPC jest wywoływana automatycznie. Ta metoda z kolei mogłaby użyć obiektu połączenia helpera XPC podobnie do wywołania metody na obiekcie wyeksportowanym przez główną aplikację.

Obsługa błędów

Oprócz wszelkich metod obsługi błędów specyficznych dla danego zadania helpera, zarówno usługa XPC jak i główna aplikacja powinny również zapewnić następujące bloki obsługi błędów XPC:

  • Obsługa przerwania – wywoływana, gdy proces na drugim końcu połączenia uległ awarii lub w inny sposób zamknął swoje połączenie. Lokalny obiekt połączenia jest zazwyczaj nadal ważny – każde przyszłe wywołanie spowoduje automatyczne utworzenie nowej instancji helpera, chyba że jest to niemożliwe – ale może być konieczne zresetowanie stanu, który helper zachowałby w przeciwnym razie.

Ręcznik jest wywoływany w tej samej kolejce co komunikaty odpowiedzi i inne handler’y, i jest zawsze wykonywany po innych komunikatach lub handler’ach bloków odpowiedzi (z wyjątkiem handler’a unieważnienia). Bezpiecznie jest wykonywać nowe żądania na połączeniu z handlerem przerwania.

  • Handler unieważnienia – wywoływany w przypadku wywołania metody invalidate lub gdy nie można było uruchomić helpera XPC. Po wywołaniu tego handler’a lokalny obiekt połączenia jest już nieważny i musi zostać odtworzony. Jest to zawsze ostatni handler wywoływany na obiekcie połączenia. Wywołanie tego bloku oznacza, że obiekt połączenia został zerwany. Nie jest możliwe wysyłanie dalszych wiadomości na połączeniu w tym momencie, czy to wewnątrz handler’a, czy gdzie indziej w twoim kodzie.

W obu przypadkach, powinieneś użyć zmiennych przypisanych do bloku, aby zapewnić wystarczającą ilość informacji kontekstowych – być może kolejkę oczekujących operacji i sam obiekt połączenia – tak aby twój kod handler’a mógł zrobić coś sensownego, takiego jak ponowne spróbowanie oczekujących operacji, zerwanie połączenia, wyświetlenie okna dialogowego błędu, lub jakiekolwiek inne działania, które mają sens w twojej konkretnej aplikacji.