如何在OSX中调试XPC服务和客户端应用程序之间的通信

时间:2017-03-13 13:16:02

标签: objective-c macos service xpc

我正在尝试编写一对简单的“客户端应用”& “XPC服务”。我能够从客户端启动xpc服务(即我可以看到在Activity监视器进程列表中运行的服务),但是当我尝试发送任何具有响应块的请求时,我收到错误:“无法与之通信帮助应用程序。“

最糟糕的是,错误并没有给我任何关于出错的信息。而且我也无法正确调试服务。据我了解,正确的方法是附加一个调试器进行处理(Debug-> Attach to process,另见here)。我在一个工作区中同时拥有客户端和服务项目。

当我从xcode运行客户端并尝试将调试器附加到已启动的服务时,以“无法附加到pid:X”错误结束。

如果我存档客户端应用程序从app文件运行它然后尝试将调试器附加到服务结果是相同的。

从我能想象的服务中记录内容的唯一方法是编写一个记录器类,将数据写入某个文件。尚未尝试过这种方法,但这看起来很疯狂。

所以我的问题是:

a)如何在收到诸如“无法与帮助应用程序通信”之类的非信息性响应时找出问题所在?

b)而且,首先调试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;
}

对于客户端应用,UI包含一堆按钮:

- (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];
             }];
        }
    }

1 个答案:

答案 0 :(得分:1)

当您无法附加调试器时,乔纳森·莱文(Jonathan Levin)的XPoCe工具会很有帮助。

您可以将日志记录NSLog()fprintf(stderr,...)添加到服务和客户端,尤其是在状态码周围。您只需要指定文件的路径即可编写stdout和stderr。 <key>StandardErrorPath</key> <string>/tmp/mystderr.log</string>

这篇关于objc.io的文章Debugging Daemons的一部分。