12

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?

sudo rm -rf
  • 29,408
  • 19
  • 102
  • 161
  • Looks as though it also needs to do `NSPropertyListSerialization`. It also seems [you're not alone](https://gist.github.com/AlanQuatermain/3209230) – CodaFi May 27 '13 at 02:58

3 Answers3

18

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.

Alex Brown
  • 41,819
  • 10
  • 94
  • 108
DrGodCarl
  • 1,666
  • 1
  • 12
  • 17
  • 1
    My problem was solved by your original answer. While I am using XPC, the whitelisting issue I encountered was in the NSCoding implementation. I was decoding a dictionary and only had NSDictionary whitelisted, but I needed to whitelist the possible types of values inside the dictionary as well like you show in your example. Thanks. – Andrew Oct 04 '15 at 04:59
  • @Andrew - Yeah, I left everything here because they all manifest their issues in very similar ways. Glad at least one of my three answers could help. – DrGodCarl Oct 06 '15 at 15:40
  • The "original answer" also solved this problem for me. I had an NSMutableArray of strings on my custom object and was using -decodeObjectOfClass:[NSMutableArray class] instead of using the -decodeObjectOfClasses:forKey: method. – Bryan Mar 01 '17 at 10:12
  • Well I'm glad to have been of service. – DrGodCarl Mar 07 '17 at 19:52
2

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.

Sam Miller
  • 23,808
  • 4
  • 67
  • 87
0

Actually it seems you need to add your custom class to the already whitelisted ones :

NSSet currentClasses = [remoteObjectInterface classesForSelector:@selector(takeThese:reply:) argumentIndex:0 ofReply:YES];


NSSet *allIncomingClasses = [currentClasses setByAddingObjectsFromSet:[NSSet setWithObjects:[Foo class], [NSNumber class], nil];

NSXPCInterface *remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(InYourFaceInterface)];
[remoteObjectInterface setClasses:allIncomingClasses forSelector:@selector(takeThese:reply:) argumentIndex:0 ofReply:YES];
xpcConnection.remoteObjectInterface = remoteObjectInterface;
Guillaume Laurent
  • 1,734
  • 14
  • 11