Как отладить связь между службой XPC и клиентским приложением в OSX

Я пытаюсь написать простую пару "клиентское приложение" и "служба XPC". Мне удалось запустить службу xpc с клиента (т.е. я вижу службу, запущенную в списке процессов монитора активности), но когда я пытаюсь отправить любой запрос, имеющий блок ответа, я получаю сообщение об ошибке: "Не удалось связаться с вспомогательное приложение."

Хуже всего то, что ошибка не дает мне никакой информации о том, что пошло не так. И я также не могу отладить службу должным образом. Как я понимаю, правильный способ сделать это - подключить к процессу отладчик (Debug->Attach to process, также см. Здесь). У меня есть и клиентские и сервисные проекты в одном рабочем пространстве.

Когда я запускаю клиент из xcode и пытаюсь присоединить отладчик к запущенному сервису, это заканчивается ошибкой "Не удалось подключиться к pid: X".

Если я архивирую клиентское приложение, запускаю его из файла приложения и затем пытаюсь подключить отладчик для обслуживания, результат будет таким же.

Единственный способ записать что-либо из сервиса, который я мог себе представить, это написать класс логгера, который бы записывал данные в какой-то файл. Я еще не пробовал такой подход, однако, мне это кажется безумным.

Итак, мой вопрос:

а) Как узнать, что пошло не так, при получении такого неинформативного ответа, как: "Не удалось связаться с приложением-помощником"?

б) А также, как правильно отлаживать службу xpc? Ссылке выше уже 5 лет, однако я вижу, что некоторые люди говорили, что "присоединиться к отладчику" не работает.

Сам код довольно прост:

Сервис XPC, реализация слушателя:

#import "ProcessorListener.h"

@implementation ProcessorListener

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
    [newConnection setExportedInterface: [NSXPCInterface interfaceWithProtocol:@protocol(TestServiceProtocol)]];
    [newConnection setExportedObject: self];
    self.xpcConnection = newConnection;

    newConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol: @protocol(Progress)];

    // connections start suspended by default, so resume and start receiving them
    [newConnection resume];

    return YES;
}

- (void) sendMessageWithResponse:(NSString *)receivedString reply:(void (^)(NSString *))reply
{
    reply = @"This is a response";
}

- (void) sendMessageWithNoResponse:(NSString *)mString
{
    // no response here, dummy method
    NSLog(@"%@", mString);
}

И основной файл для сервиса:

#import <Foundation/Foundation.h>
#import "TestService.h"

@interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
@end

@implementation ServiceDelegate

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
    // This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection.

    // Configure the connection.
    // First, set the interface that the exported object implements.
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(TestServiceProtocol)];

    // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object.
    TestService *exportedObject = [TestService new];
    newConnection.exportedObject = exportedObject;

    // Resuming the connection allows the system to deliver more incoming messages.
    [newConnection resume];

    // Returning YES from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call -invalidate on the connection and return NO.
    return YES;
}

@end

int main(int argc, const char *argv[])
{
//    [NSThread sleepForTimeInterval:10.0];
    // Create the delegate for the service.
    ServiceDelegate *delegate = [ServiceDelegate new];

    // Set up the one NSXPCListener for this service. It will handle all incoming connections.
    NSXPCListener *listener = [NSXPCListener serviceListener];
    listener.delegate = delegate;

    // Resuming the serviceListener starts this service. This method does not return.
    [listener resume];
    return 0;
}

Для клиентского приложения пользовательский интерфейс содержит несколько кнопок:

- (IBAction)buttonSendMessageTap:(id)sender {
    if ([daemonController running])
    {
        [self executeRemoteProcessWithName:@"NoResponse"];
    }
    else
    {
        [[self.labelMessageResult cell] setTitle: @"Error"];
    }
}

- (IBAction)buttonSendMessage2:(id)sender {
    if ([daemonController running])
    {
        [self executeRemoteProcessWithName:@"WithResponse"];
    }
    else
    {
        [[self.labelMessageResult cell] setTitle: @"Error"];
    }
}

- (void) executeRemoteProcessWithName: (NSString*) processName
    {
        // Create connection
        NSXPCInterface * myCookieInterface = [NSXPCInterface interfaceWithProtocol: @protocol(Processor)];

        NSXPCConnection * connection = [[NSXPCConnection alloc] initWithServiceName: @"bunldeID"]; // there's a correct bundle id there, really

        [connection setRemoteObjectInterface: myCookieInterface];

        connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(Progress)];
        connection.exportedObject = self;

        [connection resume];

        // NOTE that this error handling code is not called, when debugging client, i.e connection seems to be established
        id<Processor> theProcessor = [connection remoteObjectProxyWithErrorHandler:^(NSError *err)
                                      {
                                          NSAlert *alert = [[NSAlert alloc] init];
                                          [alert addButtonWithTitle: @"OK"];
                                          [alert setMessageText: err.localizedDescription];
                                          [alert setAlertStyle: NSAlertStyleWarning];

                                          [alert performSelectorOnMainThread: @selector(runModal) withObject: nil waitUntilDone: YES];
                                      }];

        if ([processName containsString:@"NoResponse"])
        {
            [theProcessor sendMessageWithNoResponse:@"message"];
        }
        else if ([processName containsString:@"WithResponse"])
        {
            [theProcessor sendMessageWithResponse:@"message" reply:^(NSString* replyString)
             {
                 [[self.labelMessageResult cell] setTitle: replyString];
             }];
        }
    }

0 ответов

Инструмент XPoCe Джонатана Левина полезен, когда вы не можете подключить отладчик.

Вы можете добавить логирование NSLog() или же fprintf(stderr,...)вашим услугам и клиентам, особенно в отношении кодов состояния. Вам просто нужно указать путь к файлу для записи stdout и stderr. <key>StandardErrorPath</key> <string>/tmp/mystderr.log</string>

В этой статье на objc.io есть раздел об отладке демонов.

Другие вопросы по тегам