XPC not registering classes correctly for collection - objective-c

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.

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;

Related

Why should I not separate alloc and init?

The normal way to initialise and allocate in Objective-C is
NSObject *someObject = [[NSObject alloc] init];
Why is the following not practised?
NSObject *someObject = [NSObject alloc];
[someObject init];
The main problem is that you might end up using the wrong object.
init is special in many classes as it might just release the receiver and instead create a new object that resides at a different address. So your someObject then points to the wrong (uninitialized) instance.
There are a lot of framework classes that use the arguments of the init method to decide which kind of specialized subclass is best to use. This frequently happens with class clusters like NSString or NSArray but it can really happen with each kind of object.
One place where you can see this special behavior of initializers is ARC: It explicitly declares that the init family of methods eats up the receiver and returns a +1 retained object. This would not be necessary if initializers would just always return the receiver.
Of course you could fix your code by just doing another assignment:
NSObject *someObject = [NSObject alloc];
someObject = [someObject init];
This would fix the problem. But there's also no sense in doing it.
From the Object Initialization official documentation:
Because an init... method might return nil or an object other than the one explicitly allocated, it is dangerous to use the instance returned by alloc or allocWithZone: instead of the one returned by the initializer
Another reason from the same document is that:
Once an object is initialized, you should not initialize it again
given this example:
NSString *aStr = [[NSString alloc] initWithString:#"Foo"];
aStr = [aStr initWithString:#"Bar"];
where:
the second initialization in this example would result in NSInvalidArgumentException being raised.
Because it is less simple and more error-prone.
An allocated but not initialised object is useless, so it make sense to put allocation and initialisation in one line. If they are separated, there is more possibility for errors and bugs if the two lines are not directly after each other (perhaps after refactoring), which may lead to errors while trying to use an uninitialised object.
There simply isn't a single good reason to alloc and init in separate lines, and many reasons against it.
As per my understanding an allocated object makes no sense without it being initialized,
if you alloc an object first and then later plan to initialize it, there might be a case that you may forget to initialize the object and give a direct call to any of its instance method which would result in run time error.
Example:
NSString *str = [NSString alloc];
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
NSLog(#"%ld",str.length);
When i run the above code i get this in my console
Did you forget to nest alloc and init?
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -length only defined for abstract class. Define -[NSPlaceholderString length]!'
if i would do the below I would still get the exception as str is not being initialized because whatever is being initialized is not being consumed or pointed by str
[str init];
Hence if you want to do it in two lines it should be like this
NSObject *someObject = [NSObject alloc];
someObject = [someObject init];
But it's always better to keep them nested
NSObject *someObject = [[NSObject alloc]init];
If you plan on doing it on single line then use the new keyword which servers the purpose of allocation and initialization on a single line.
Example: YourClass *object_ofClass = [YourClass new];

Getting the class type for a nil object?

If I have an object that is already allocated, then doing object.class returns a non-nil value. So far so good. But, if the object has not yet been allocated, then accessing object.class returns nil.
I want to allocate an object based on its type dynamically, so for example:
#property NSArray *myArray;
...
// myArray is nil so far
self.myArray = [_myArray.class new];
However, I can't do this because _myArray.class is returning nil. So how would I determine the class type of a nil instance?
Update:
It is in fact possible. Check out my answer below.
You cannot determine the class of a nil instance, because it does not have one: it can be, quite literally, of any type derived from the type of the variable. For example, NSMutableArray is perfectly compatible with NSArray:
NSArray *myArray = [NSArray new]; // OK
NSArray *myArray = [NSMutableArray new]; // Also OK
Since the run-time capabilities of different subclasses can vary a lot, it is always up to your program to decide what kind of objects it wants.
Objective-C is a duck-typed language. This means that there are several things you can or can't do, and one of the things you can't is statically get a reference to the type of a variable.
Specifically, in your expression:
[_myArray.class new]
First, _myArray.class is evaluated, and then the result is sent the new message. Since _myArray is nil to begin with, _myArray.class returns nil as well, and the new message will return nil too, because sending any message to nil returns nil (or the closest representation to zero the return type has). This is why it doesn't work.
I suspect you come from a strongly-typed language like C#; what you're doing right now is the equivalent of Foo foo = (Foo)Activator.CreateInstance(foo.GetType()), which is sure to fail because foo.GetType() will either not compile or throw an exception (depending on if it's a class field or a local variable) since it was never assigned a value. In Objective-C, it compiles but it doesn't works. What you would want is Activator.CreateInstance(typeof(Foo)), but notice that Foo is now hardcoded here too, so you might as well just create a new Foo().
You say that the compiler "knows the type" of the object. This is not exactly true. First, NSArray and NSMutableArray are the root classes of the NSArray class cluster. This means that both are abstract, and [NSArray alloc] and [NSMutableArray alloc] return an instance of a subclass (NSCFArray last time I checked, and possibly something else; I recall seeing _NSArrayM). Maybe [NSArray new] works, but it's not giving you a plain NSArray.
Second, type safety is not enforced. Consider this code:
id foo = #"foo";
NSArray* bar = foo; // no warning!
So even though the compiler thinks that bar is an NSArray, it's in fact a NSString. If we plug in your code:
id foo = #"foo";
NSArray* bar = foo; // no warning!
NSArray* baz = [bar.class new];
baz is now an NSString as well. Since you ask for the runtime class of bar, the compiler has nothing to do with the operations.
And precisely because of that kind of behavior, you should probably instantiate your object with a class that you know, using [NSArray new] instead of trusting _myArray to be non-nil, and to be what you think it is.
You must init the property , or it will be nil , send a message to a nil object , it will return nil , so ,you must first init the array like _array = [[NSArray alloc] init];
So, for anyone wondering if this is possible, it is:
objc_property_t property = class_getProperty(self.class, "myArray");
const char * const attrString = property_getAttributes(property);
const char *typeString = attrString + 1;
const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL);
const char *className = typeString + 2;
next = strchr(className, '"');
size_t classNameLength = next - className;
char trimmedName[classNameLength + 1];
strncpy(trimmedName, className, classNameLength);
trimmedName[classNameLength] = '\0';
Class objectClass = objc_getClass(trimmedName);
NSLog(#"%#", objectClass);
Output:
NSArray
Done with the help of extobjc.
Nil has no class type
In Objective-C the actual class on an instance variable is only determined at runtime. So, you can't know the class of a nil object.
This is not an issue in your situation since you only need to do:
NSArray *myArray = [NSArray new];
Or
NSArray *myArray = [[NSArray alloc] init];
In Objective-C most decisions are deferred to the runtime
(as much as possible)
Objective-C is a runtime oriented language, which means that when it's
possible it defers decisions about what will actually be executed from
compile & link time to when it's actually executing on the runtime.
This gives you a lot of flexibility in that you can redirect messages
to appropriate objects as you need to or you can even intentionally
swap method implementations, etc.
This requires the use of a runtime
which can introspect objects to see what they do & don't respond to
and dispatch methods appropriately. If we contrast this to a language
like C. In C you start out with a main() method and then from there
it's pretty much a top down design of following your logic and
executing functions as you've written your code. A C struct can't
forward requests to perform a function onto other targets.
Source: Understanding the Objective-C Runtime

ARC autoreleases too early (?)

I have a method call in class A:
GDataXMLElement *infoElement = [self getElementFromFilePath:filePath];
NSString *testStringA = [infoElement attributeForName:#"someAttribute"].stringValue;
and the method implementation in class B:
-(GDataXMLElement*)getElementFromFilePath:(NSString*)filePath {
NSData *xmlData = [NSData dataWithContentsOfFile:filePath];
GDataXMLDocument *infoXMLDoc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:nil];
NSArray *infoArray = [infoXMLDoc.rootElement elementsForName:#"test"];
GDataXMLElement *returnElement = (GDataXMLElement*)infoArray[0];
NSString *testStringB = [returnElement attributeForName:#"someAttribute"].stringValue;
return returnElement;
}
The returnElement at the end of the method in class B is perfectly initialized, and testStringB string contains the correct value.
But in Class A, the contents of InfoElement are gone, and testStringA is nil.
I suspect that ARC is releasing GDataXMLDocument too early, and was able to stop this behaviour by tying the document to a property in class B:
#property (nonatomic,strong) GDataXMLDocument *infoXMLDoc;
But I am a little unsatisfied with this solution. I'll never use that property again, I just need the element to parse it one time only. If it is possible to stop the release with a property, is there also a way to do this within the method? I tried the __strong qualifier like this:
GDataXMLDocument __strong *infoXMLDoc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
but that didn't help. So my questions, assuming ARC is indeed responsible:
1. Is is possible to tell ARC within a method to not release an object?
2. What did I not understand about the behaviour here? I am using ARC for some time now, this is the first time that I am stuck.
The GDataXMLNode.h header says:
it is up to the code that created a document to retain it for as long
as any references rely on nodes inside that document tree.
The node you return from getElementFromFilePath depends on the parent (the GDataXMLDocument), but that is going to be released by ARC. You must retain the GDataXMLDocument somewhere for as long as you reference nodes and elements inside it. These are the semantics of the GDataXML* classes and you must follow them.
I can't compile so this's just an educated guessing, but I suspect the problem is that you return a pointer to an object that is allocated and released inside the method:
GDataXMLElement *returnElement = (GDataXMLElement*)infoArray[0];
As you see you don't alloc returnElement, so ARC have no way to understand that you need it. It simply release infoArray when you exit from the method.
If you copy the value (something like [(GDataXMLElement*)infoArray[0] copy] ) it should works.

How to check assignment since addObject doesn't access setter?

I just noticed that calling addObject: on an NSMutableArray doesn't access that array's setter.
E.g., for NSMutableArray self.myArray, [self.myArray addObject:object] does not use [self setMyArray:array] to add the object.
Previously I have been using custom setters and getter to check assignment before assigning; e.g., if I wanted an array that only accepted objects of class MyClass, I would do the following:
- (void)setMyArray:(NSMutableArray *)myArray
{
for (id object in myArray)
{
if (![object isKindOfClass:[MyClass class]]) return;
}
_myArray = myArray;
}
- (NSMutableArray *)myArray
{
if (!_myArray) _myArray = [[NSMutableArray alloc] init];
_myArray = myArray;
}
How do I go about achieving this same functionality when changing the array via addObject:, removeObject:, and other similar functions that may circumvent the setter?
Generally this kind of problem is the reason why NSMutableArray is usually avoided in preference of NSArray.
This is the simple solution, use NSArray instead of NSMutableArray:
self.myArray = [self.myArray arrayByAddingObject:foo];
However, if the array is really big that will cause performance issues. Then you've got two options:
you can have your own addObjectToMyArray: method in your class and always use that
you can create an NSArrayController and use that to access your array. It will implement key value observing and bindings and all of that stuff.
NSMutableArray is designed to perform addObject: with as few CPU instructions as possible and therefore does not proved any way for external code to be notified that the object was added. You have to have some other class wrapped around it.
Do not try to subclass NSMutableArray, because it is a "class cluster" making subclasses extremely complicated.
If what you wish to do is ensure objects in the array are of a particular class then this answer to the question "NSMutableArray - force the array to hold specific object type only" provides code to do exactly that.
If you wish to do other checks on assignment then you can use the code in that answer as a starting point.

Missing sentinel in method dispatch

I want to create a subclass of NSMutableArray and need to override the -initWithObjects: method.
But How to call [super xxx];?
- (id) initWithObjects:(id)firstObj, ... {
[super initWithObjects:firstObj]; // Error: Missing sentinel in method dispatch
// Error: The result of a delegate init call must be immediately returned or assigned to "self"
}
Thanks.
Then "missing sentinel" message refers to the missing nil termination. In fact, according to font-of-all-knowledge-Wikipedia:
The name of the nil that terminates a variable length list of parameters in Objective-C
also: Sentinel node, an object to represent the end of a data structure
also: Sentinel value, a value used to terminate a loop
also: In network protocols such as Bisync, sentinel values indicate where frames start and end
You can't. As discussed in the documentation for NSArray:
You might want to implement an initializer for your subclass that is
suited to the backing store that the subclass is managing. The NSArray
class does not have a designated initializer, so your initializer need
only invoke the init method of super. The NSArray class adopts the
NSCopying, NSMutableCopying, and NSCoding protocols; if you want
instances of your own custom subclass created from copying or coding,
override the methods in these protocols.
So you can assign self = [super init]; and add the objects from your initialiser to the resulting object. Indeed, because of the way that NSArray is implemented, calling any -initWith… method is likely to return an instance of a different NSArray subclass.
Notice that the documentation also discusses alternatives to subclassing NSArray that may be easier, more reliable or better in some other way.
Subclassing NSArray/NSMutableArray doesn't work like subclassing most classes. NSArray is a class cluster, please see subclassing notes from the NSArray documentation.
Now, for your specific question, subclassing va_list methods is a bit tricky, there are a number of ways to handle this. The 'safest' would be to process your va_list into an NSArray and pass that into another method that dealt with whatever you wanted. The other, slightly less portable, slightly hackier way is to create a new va_list list on the stack to pass through.
id __unsafe_unretained * stack = (typeof(stack))calloc(numOfObjects, sizeof(id));
//filloutStack
[super initWithObjects:*stack, nil];
free(stack);
Subclassing Apples Collection classes isn't that difficult — if you use a tiny trick (see also: cocoawithlove).
A subclass is a "is-a" relationship in object-orientated Design. But there are also "has-a" relationships, i.e. wrappers.
If you would try to create a subclass of NSArray by using a pure is-a relationship, I guess, it would be kind of hard, as you would have to do C-level memory management.
But if you add a has-a relationship — or: create a wrapper — at the same time, you can the subcalssing quite easily: Just make your custom array class have a member of a regular NSArray. Now override its method by forwarding the calls to the member object. I showed this in this post, where I just add objects, that pass a certain test.
But you will see, that I didn't implement the method you talked about correctly, but I raise a error. The reason is: that method is a variadic methods, that has a variable number of objects you can pass in — and to handle this, you have to to a bit of work. cocoawithlove has an great article about it.
For you — if using that has-a trick — it could look like
- (id) initWithObjects:(id)firstObj, ... {
if (self = [super init]) {
_realArray = [[NSMutableArray alloc] initWithCapacity:1];
}
va_list args;
va_start(args, firstObj);
for (id obj = firstObj; obj != nil; obj = va_arg(args, id))
{
[self.realArray addObject:obj];
}
va_end(args);
return self;
}
Try
self = [super initWithObjects:firstObj,nil];