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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With