Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XPC not registering classes correctly for collection

I'm using XPC in one of my apps on 10.8. It's got the standard setup with protocols defined for exported interface and the remote interface. The problem I run into is with one of my methods on the exported interface.

I have a model class, lets just call it Foo. This class conforms to NSSecureCoding, implements +supportsSecureCoding, and encodes/decodes the internal properties correctly using the secure coding methods. When passing this object through a method on my exported interface that only involves a single instance, it works fine.

The problem occurs when I want to pass a collection of these objects, or a NSArray of Foo objects. Here's an example of what the signature on the exported interface looks like:

- (void)grabSomethingWithCompletion:(void (^)(NSArray *foos))completion;

And I've whitelisted the Foo class, as noted in the documentation:

NSSet *classes = [NSSet setWithObject:Foo.class];
[exportedInterface setClasses:classes forSelector:@selector(grabSomethingWithCompletion:) argumentIndex:0 ofReply:YES];

Now this should make it so that this array can be safely copied across the process and decoded on the other side. Unfortunately this doesn't seem to be working as expected.

When calling the method on the exported protocol, I receive an exception:

Warning: Exception caught during decoding of received reply to message 'grabSomethingWithCompletion:', dropping incoming message and calling failure block.

Exception: Exception while decoding argument 1 of invocation: return value: {v} void target: {@?} 0x0 (block) argument 1: {@} 0x0

Exception: value for key 'NS.objects' was of unexpected class 'Foo'. Allowed classes are '{( NSNumber, NSArray, NSDictionary, NSString, NSDate, NSData )}'.

This almost seems like it didn't even register the whitelisting I performed earlier. Any thoughts?

like image 204
sudo rm -rf Avatar asked May 27 '13 01:05

sudo rm -rf


2 Answers

EDIT 2: It depends on where you've whitelisted Foo. It needs to be whitelisted from within whatever is calling grabSomethingWithCompletion:. For instance, if you have a service that implements and exposes:

- (void)takeThese:(NSArray *)bars reply:(void (^)(NSArray *foos))completion;

Then you need the service side to whitelist Bar for the incoming connection:

// Bar and whatever Bar contains.
NSSet *incomingClasses = [NSSet setWithObjects:[Bar class], [NSString class], nil];
NSXPCInterface *exposedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(InYourFaceInterface)];
[exposedInterface setClasses:incomingClasses forSelector:@selector(takeThese:reply:) argumentIndex:0 ofReply:NO];

// The next line doesn't do anything.
[exposedInterface setClasses:incomingClasses forSelector:@selector(takeThese:reply:) argumentIndex:0 ofReply:YES];
xpcConnection.exposedInterface = exposedInterface;

That second section has to go on the other end of the connection, whatever is talking to your service:

NSSet *incomingClasses = [NSSet setWithObjects:[Foo class], [NSNumber class], nil];
NSXPCInterface *remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(InYourFaceInterface)];
[remoteObjectInterface setClasses:incomingClasses forSelector:@selector(takeThese:reply:) argumentIndex:0 ofReply:YES];
xpcConnection.remoteObjectInterface = remoteObjectInterface;

In summary, whatever is receiving strange objects needs to be the one whitelisting the strange objects. Not sure if this was your problem, but I'm sure it will be somebody's.

EDIT: Now that I've been working with XPC for a while longer, I realize that my answer, while solving a problem, does not solve your problem. I've run into this now a couple different times and I'm still not sure how to fix it outside of implementing my own collection class, which is less than ideal.

Original Answer: I know it has been quite some time since you asked this, but after a ton of searching with no one answering this question, I thought I'd post my answer for what was causing it (there may be other causes, but this fixed it for me).

In the class that conforms to NSSecureCoding, in the initWithCoder: method, you need to explicitly decode collections by passing in a set of all possible classes contained within the collection. The first two are standard examples of decoding, and the last one is decoding a collection:

if (self = [super init]) {
    self.bar = [aDecoder decodeInt64ForKey:@"bar"];
    self.baz = [aDecoder decodeObjectOfClass:[Baz class] forKey:@"baz"];
    NSSet *possibleClasses = [NSSet setWithObjects:[Collection class], [Foo class], nil];
    self.foo = [aDecoder decodeObjectOfClasses:possibleClasses forKey:@"foo"];
}

So if you collection is a set containing NSStrings, possible classes would be [NSSet class] and [NSString class].

I'm sure you've moved on from this problem, but maybe someone else needs this answer as much as I did.

like image 189
DrGodCarl Avatar answered Nov 15 '22 18:11

DrGodCarl


I encountered this same problem, I had to explicitly whitelist NSArray* as well

NSSet *classes = [NSSet setWithObjects: [Foo class], [NSArray class], nil];

Which is a bit counterintuitive since the documentation does not mention this requirement.

like image 1
Sam Miller Avatar answered Nov 15 '22 19:11

Sam Miller