Articles

XPC serviços em macOS app

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

Follow

> 4 de setembro, 2020 – 9 min leia-se

>

Antes de XPC usávamos para pegar Tomadas e Mensagens de Máquinas (Mach Ports).

O mecanismo XPC oferece uma alternativa aos soquetes (ou Mach Services usando MIG) para IPC. Poderíamos ter, por exemplo, um processo que atua como “servidor” esperando que os clientes acessem sua API e forneçam algum serviço.

XPC Services on applications

Quando falamos de XPC Services (S maiúsculo), estamos nos referindo ao pacote chamado XPC Service. Bundles no ecossistema Apple refere-se a entidades representadas por uma estrutura de directórios específica. O Bundle mais comum que você encontra são os Application Bundles. Se você clicar com o botão direito em qualquer aplicação (por exemplo Chess.app) e selecionar Mostrar conteúdo, o que você encontrará é uma estrutura de diretórios. De volta ao XPC, as aplicações podem ter pacotes de serviços XPC. Você vai encontrá-los dentro do diretório Content/XPCServices/ dentro do pacote de aplicações. Você pode procurar em seu diretório /Applications e ver quantas das aplicações dependem do XPC Services.

Você também pode ter XPC Services dentro do Frameworks (Que são outro tipo de pacote).

Benefícios Adicionais do XPC Services

Usar XPC Services em nossas aplicações nos permite quebrar algumas funcionalidades em módulos separados (The XPC Service). Podemos criar um Serviço XPC que pode ser encarregado de executar algumas tarefas dispendiosas, mas pouco freqüentes. Por exemplo, alguma tarefa criptográfica para gerar números aleatórios.

Outro benefício adicional é que o Serviço XPC roda em seu próprio processo. Se esse processo falhar ou morrer, ele não afeta nossa aplicação principal. Imagine que a sua aplicação suporta plugins definidos pelo usuário. E os plugins são construídos usando o XPC Services. Se eles estiverem mal codificados e travarem, eles não afetarão a integridade da sua aplicação principal.

Um benefício adicional para o Serviço XPC é que eles podem ter seus próprios direitos. A aplicação só exigirá o direito quando fizer uso de um serviço fornecido pelo Serviço XPC que exija o direito. Imagine que você tem um aplicativo que utiliza localização, mas apenas para características específicas. O usuário poderia mover esses recursos para um Serviço XPC e adicionar o direito de localização apenas a esse Serviço XPC. Se o seu usuário nunca precisar do recurso que usa a localização, ele não será solicitado por permissões, tornando o uso do seu aplicativo mais confiável.

XPC e nosso amigo launchd

launchd é o primeiro processo a ser executado em nosso sistema. Ele está encarregado de lançar e gerenciar outros processos, serviços e daemons. launchd também está encarregado de agendar tarefas. Portanto, faz sentido que o launchd também seja responsável pelo gerenciamento do XPC Services.

XPC Service pode ser interrompido se ele estiver ocioso por um longo tempo, ou ser gerado sob demanda. Todo o gerenciamento é feito pelo launchd, e não precisamos fazer nada para que ele funcione.

launchd tem informações sobre disponibilidade de recursos em todo o sistema e pressão de memória, quem melhor para tomar decisões sobre como usar os recursos do nosso sistema da forma mais eficaz do que lançard

Implement XPC Services

Um serviço XPC é um pacote no diretório Content/XPCServices do pacote principal de aplicações; o pacote de serviços XPC contém um arquivo Info.plist, um executável, e quaisquer recursos necessários ao serviço. O serviço XPC indica qual função chamar quando o serviço recebe mensagens chamando xpc_main(3) Mac OS X Developer Tools Manual Page da sua função principal.

Para criar um serviço XPC em Xcode, faça o seguinte:

  1. Adicionar um novo alvo ao seu projeto, usando o modelo de serviço XPC.
  2. Adicionar uma fase Copy Files às configurações de compilação da sua aplicação, que copia o serviço XPC no diretório Contents/XPCServices do pacote principal da aplicação.
  3. Adicionar uma dependência às configurações de compilação da sua aplicação, para indicar que depende do pacote de serviços XPC.
  4. Se você está escrevendo um serviço XPC de baixo nível (baseado em C), implemente uma função principal mínima para registrar seu gerenciador de eventos, como mostrado na lista de códigos a seguir. Substitua my_event_handler pelo nome da sua função event handler.
int main(int argc, const char *argv) {
xpc_main(my_event_handler);
// The xpc_main() function never returns.
exit(EXIT_FAILURE);
}

Se você está escrevendo um serviço de alto nível (baseado em Objective-C) usando NSXPCConnection, primeiro crie uma classe de delegado de conexão que esteja de acordo com o protocolo NSXPCListenerDelegate. Depois, implemente uma função principal mínima que cria e configura um objeto ouvinte, como mostrado na seguinte listagem de código.

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

Usando o serviço

A forma como você usa um serviço XPC depende se você está trabalhando com a API C (XPC Services) ou com a API Objective-C (NSXPCConnection).

Para usar a API NSXPCConnection, você deve criar o seguinte:

  • Uma interface. Isto consiste principalmente de um protocolo que descreve quais métodos devem ser chamáveis a partir do processo remoto. Isto é descrito em Projetando uma Interface
  • Um objeto de conexão em ambos os lados. No lado do serviço, isto foi descrito anteriormente em Criando o Serviço. No lado do cliente, isto é descrito em Conectando e Usando uma Interface.
  • Um ouvinte. Este código no serviço XPC aceita conexões. Isto está descrito em Aceitando uma conexão no helpper. Messages.

Arquitetura Geral

Arquitetura Geral

Ao trabalhar com aplicações helper baseadas em NSXPCConnection, tanto a aplicação principal como o helper têm uma instância de NSXPCConnection. A aplicação principal cria o seu próprio objecto de ligação, o que faz com que o helper seja lançado. Um método delegado no helper é passado no seu objecto de ligação quando a ligação é estabelecida. Isto é ilustrado na Figura 4-1.

Cada objecto NSXPCConnection fornece três características chave:

  • Uma propriedade ExportInterface que descreve os métodos que devem ser disponibilizados para o lado oposto da ligação.
  • Uma propriedade ExportObject que contém um objecto local para lidar com chamadas de métodos vindas do outro lado da ligação.
  • A capacidade de obter um objecto proxy para métodos de chamada do outro lado da ligação.

Quando a aplicação principal chama um método em um objeto proxy, o objeto NSXPCConnection do serviço XPC chama esse método no objeto armazenado em sua propriedade exportedObject.

Simplesmente, se o serviço XPC obtém um objeto proxy e chama um método sobre esse objeto, o objeto principal da aplicação NSXPCConnection chama esse método sobre o objeto armazenado em sua propriedade exportedObject

Desenhando uma Interface

A API NSXPCConnection aproveita os protocolos Objective-C para definir a interface programática entre a aplicação de chamada e o serviço. Qualquer método de instância que você queira chamar do lado oposto de uma conexão deve ser explicitamente definido em um protocolo formal. Por exemplo:

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

Porque a comunicação sobre XPC é assíncrona, todos os métodos no protocolo devem ter um tipo de retorno de vazio. Se você precisar retornar dados, você pode definir um bloco de resposta como este:

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

Um método pode ter apenas um bloco de resposta. Entretanto, como as conexões são bidirecionais, o helper do serviço XPC também pode responder chamando métodos na interface fornecida pela aplicação principal, se desejado.

Cada método deve ter um tipo de retorno de vazio, e todos os parâmetros para métodos ou blocos de resposta devem ser um ou outro:

  • Tipos aritméticos (int, char, float, double, uint64_t, NSUInteger, e assim por diante)
  • BOOL
  • C strings
  • C estruturas e arrays contendo apenas os tipos listados acima
  • Objective-C objects que implementam o protocolo NSSecureCoding.

Importante: Se um método (ou seu bloco de resposta) tem parâmetros que são classes de coleção Objective-C (NSDictionary, NSArray, etc.), e se você precisa passar seus próprios objetos personalizados dentro de uma coleção, você deve dizer explicitamente ao XPC para permitir essa classe como um membro desse parâmetro de coleção.

Conectando e Usando uma Interface

Após ter definido o protocolo, você deve criar um objeto de interface que o descreva. Para isso, chame o método interfaceWithProtocol: na classe NSXPCInterface. Por exemplo:

NSXPCInterface *myCookieInterface =
;

Após ter criado o objeto de interface, dentro da aplicação principal, você deve configurar uma conexão com ele chamando o método initWithServiceName:. Por exemplo:

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

Note: Para se comunicar com serviços XPC fora do seu pacote de aplicativos, você também pode configurar uma conexão XPC com o método initWithMachServiceName:.

Neste ponto, a aplicação principal pode chamar o objeto remotoObjectProxy ou remoteObjectProxyWithErrorHandler: métodos no objeto myConnection para obter um objeto proxy.

Este objeto atua como um proxy para o objeto que o serviço XPC definiu como seu objeto exportado (definindo a propriedade exportObject). Este objeto deve estar de acordo com o protocolo definido pela propriedade RemoteObjectInterface.

Quando sua aplicação chama um método no objeto proxy, o método correspondente é chamado no objeto exportado dentro do serviço XPC. Quando o método do serviço chama o bloco de resposta, os valores dos parâmetros são serializados e enviados de volta para a aplicação, onde os valores dos parâmetros são deserializados e passados para o bloco de resposta. (O bloco de resposta é executado dentro do espaço de endereços da aplicação.)

Note: Se você quiser permitir que o processo de helper chame métodos em um objeto de sua aplicação, você deve definir as propriedades exportInterface e exportObject antes de continuar a chamada. Estas propriedades são descritas mais à frente na próxima secção.

Acepting a Connection in the Helper

Quando um helper baseado em NSXPCConnection recebe a primeira mensagem de uma ligação, o método do participante ouvinte listener:shouldAcceptNewConnection: é chamado com um objecto ouvinte e um objecto de ligação. Este método permite decidir se aceita ou não a conexão; deve retornar SIM para aceitar a conexão ou NÃO para recusar a conexão.

Note: O helper recebe um pedido de conexão quando a primeira mensagem real é enviada. O método de currículo do objeto de conexão não faz com que uma mensagem seja enviada.

Além de tomar decisões de política, este método deve configurar o objeto de conexão. Em particular, assumindo que o helper decide aceitar a conexão, ele deve definir as seguintes propriedades na conexão:

  • exportedInterface – um objeto de interface que descreve o protocolo para o objeto que você deseja exportar. (A criação deste objeto foi descrita anteriormente em Conectando e Usando uma Interface.)
  • exportedObject – o objeto local (geralmente no helper) para o qual as chamadas de método do cliente remoto devem ser entregues. Sempre que o extremo oposto da conexão (geralmente na aplicação) chama um método no objeto proxy da conexão, o método correspondente é chamado no objeto especificado pela propriedade exportObject.

Após definir essas propriedades, ele deve chamar o método de retomada do objeto de conexão antes de retornar SIM. Embora o delegado possa adiar o resumo da chamada, a conexão não receberá nenhuma mensagem até que o faça.

Enviar mensagens

Enviar mensagens com NSXPC é tão simples quanto fazer uma chamada de método. Por exemplo, dada a interface myCookieInterface (descrita nas seções anteriores) no objeto de conexão XPC myConnection, você pode chamar o método feedMeACookie assim:

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

Quando você chama esse método, o método correspondente no helper XPC é chamado automaticamente. Esse método, por sua vez, poderia usar o objeto de conexão do helper XPC similarmente para chamar um método no objeto exportado pela aplicação principal.

Handling Errors

Além de qualquer método de tratamento de erros específico para uma determinada tarefa do helper, tanto o serviço XPC quanto a aplicação principal devem também fornecer os seguintes blocos de tratamento de erros XPC:

  • Interruption handler – chamado quando o processo na outra extremidade da conexão falhou ou fechou sua conexão. O objeto de conexão local normalmente ainda é válido – qualquer chamada futura irá gerar automaticamente uma nova instância de helper a menos que seja impossível fazê-lo – mas você pode precisar redefinir qualquer estado que o helper teria mantido.

O handler é invocado na mesma fila que as mensagens de resposta e outros manipuladores, e é sempre executado após quaisquer outras mensagens ou manipuladores de blocos de resposta (exceto para o manipulador de invalidação). É seguro fazer novas solicitações na conexão a partir de um manipulador de interrupção.

  • Alter manipulador de validação – chamado quando o método invalidado é chamado ou quando um helper XPC não pôde ser iniciado. Quando este manipulador é chamado, o objeto de conexão local não é mais válido e deve ser recriado. Este é sempre o último manipulador chamado em um objeto de conexão. Quando este bloco é chamado, o objeto de conexão foi demolido. Não é possível enviar mais mensagens sobre a conexão naquele ponto, seja dentro do manipulador ou em outro lugar no seu código.

Em ambos os casos, você deve usar variáveis em bloco para fornecer informação contextual suficiente – talvez uma fila de operações pendentes e o próprio objeto de conexão – para que seu código de manipulador possa fazer algo sensato, como tentar novamente operações pendentes, destruir a conexão, exibir um diálogo de erro, ou qualquer outra ação que faça sentido na sua aplicação particular.