【发布时间】:2017-08-03 12:39:20
【问题描述】:
我正在尝试编写一对简单的“客户端应用程序”和“XPC 服务”。我能够从客户端启动 xpc 服务(即我可以在活动监视器进程列表中看到正在运行的服务),但是当我尝试发送具有响应块的任何请求时,我收到一个错误:“无法与一个帮助应用程序。”
这里最糟糕的是,错误并没有给我任何关于哪里出了问题的信息。而且我也无法正确调试服务。据我了解,执行此操作的正确方法是将调试器附加到进程(调试->附加到进程,另请参阅here)。我在一个工作区中同时拥有客户和服务项目。
当我从 xcode 运行客户端并尝试将调试器附加到启动的服务时,以“无法附加到 pid:X”错误结束。
如果我归档客户端应用程序,从应用程序文件运行它,然后尝试将调试器附加到服务,结果是相同的。
我能想象的从服务中记录某些内容的唯一方法是编写一个记录器类,它将数据写入某个文件。还没有尝试过这种方法,但是这对我来说看起来很疯狂。
所以我的问题是:
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];
}];
}
}
【问题讨论】:
标签: objective-c macos service xpc