Ternary Operator

April 9, 2008

Sending Up Custom Types As Parameters In A SOAP Request, On OS X

Filed under: Uncategorized — Tags: , , , — James @ 2:35 pm

After having created a WSMethodInvocationRef, you need to tell it what parameters to send up in the web service request. You do this with the WSMethodInvocationSetParameters() call which takes a CFDictionaryRef and a CFArrayRef. The CFArrayRef holds all of the parameter names (the keys in the dictionary) and the array order is the order in which the parameters will be serialized.

Since all of this is Carbon stuff, you can only serialize CFTypes; Apple’s web service API doesn’t know how to handle any Objective-C objects (with the normal exceptions of the objects which are toll-free bridged to their Carbon equivalents). You also cannot do fancy things like serailizing a parameter that stores data in XML attributes. This is somewhat limiting.

Fortunately, Apple lets you set a custom serializer. This lets us get away with a lot more stuff. I spent a few days playing around with it and eventually came up with a strategy that lets me serialize everything I neeed. It has a cost though: it’s far too complicated and I completely lose out on all of Apple’s default serializers. This is probably not the best way to do it; but it’s what I had working when I decided to move on to getting other stuff done.

Every object that I’m interested in serializing conforms a protocol, BBSerializable. This protocol includes an implementation of

-(NSString*)xmlWithRootName:(NSString*)

which returns the appropriate XML for that object.

Instead of the typical parameter dictionary, I set up a special one where the value for each parameter name key is just the parameter name (So if the key is @”Message” the value will also be @”Message”). This causes the WS API to try to serialize each parameter as if it were a CFStringRef. Coincidentally, I replace the default CFStringRef serailizer with my own. The custom serializer pulls the actual parameter out of another dictionary and checks its type. If it’s a basic type (like NSString*), it returns the seralization; if it’s not, it checks to see if it conforms to BBSerializable and if it does, asks it to serialize itself. (As a last resort, we serialize it based on its response to the -description message, but that’s obviously not ideal).

Here’s the implementation of the custom serializer. The only tricky part is remembering to CFRetain the result before returning it. Perhaps it’s just that I’m not as good with the Carbon rules as I am with Cocoa’s, but it feels like I’m over-retaining here; but I get retain_underflow exceptions if I don’t so I do it.

CFStringRef stringSerializer(WSMethodInvocationRef invocation, CFTypeRef obj, void *info)
{
   NSString *paramName = (NSString*)obj; //since the param value passed into the WS API is the param name, it makes the API pass that name to us as the object to serialize. Little does it know...
   NSDictionary *paramDictionary = (NSDictionary*) info; //The real parameter dictionary is passed in as the context info

   NSString *result = nil;

   id param = [paramDictionary objectForKey:paramName]; //pull out the actual parameter
   if([param isKindOfClass:[NSString class]]) //Do our own serialization of NSString
   {
      result = [NSString stringWithFormat:@"%@", paramName, param, paramName];
   }
   else if([param conformsToProtocol:@protocol(BBSerializable)])  //or ask the object to serialize itself
   {
      result = [param performSelector:@selector(xmlWithRootName:) withObject:[NSString stringWithFormat:@"%@", paramName]];
   }
   //We just have to do our best if the first two ideas fail
   else
   {
      result = [NSString stringWithFormat:@"%@", paramName, [param description], paramName];
   }

   if(!result)
      result = [NSString stringWithFormat:@"", paramName];

   return (CFStringRef)CFRetain((CFStringRef) result); //The WS stuff will CFRelease this string, so it needs to be CFRetained to balance it
}

Setting the custom serializer is straight-forward as well. I’m conjuring up a serializationContext out of the heap (and freeing it in the dealloc method of this class. This is a terrible way to do it, but I got bored).

//At this point, I've already set up NSDictionary *paramsCopy (which contains the actual parameters)

serializationContext = malloc(sizeof(WSClientContext));
serializationContext->version = 0;
serializationContext->info = (void*)paramsCopy;
serializationContext->retain = NULL;
serializationContext->release = NULL;
serializationContext->copyDescription = NULL;
WSMethodInvocationAddSerializationOverride(request, CFStringGetTypeID(), (WSMethodInvocationSerializationProcPtr)stringSerializer, serializationContext);

And that’s all there is to it. It’s much, much, much harder than it should be; but that’s why it’s fun. I guess.

No Comments Yet »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.