Ternary Operator

April 11, 2008

Quick Link

Filed under: Uncategorized — Tags: , , — James @ 9:59 am

Matt Gemmell has released an updated version of his Objective-C Twitter API.

This is interesting both because it’s an easy-to-use Twitter API, but also because it’s iPhone compatible; it’s a small and well-written code base that clearly demonstrates some of the practical stuff needed for Mac/iPhone cross-development. Anyway, I thought it was interesting.

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.

April 4, 2008

Processing Command Arguments in Foundation, The Easy Way

Filed under: Uncategorized — Tags: , — James @ 10:19 pm

I needed to write a small commandline utility. I could’ve done it in C, but I like Cocoa so I’m doing it in ObjC. It’s great.

Anyway, I needed to parse the arguments to my utility and so I started trying to remember the best way to pull what I needed out of char**argv; I seemed to recall a built-in function on Unix so I started Googling…

…and I found this great post by Greg Miller describing how to do it with NSUserDefaults.

This almost makes up for the travesty that is web services on the platform.

Thanks, Apple!

Making SOAP Calls From OS X, The Easy Way

Filed under: Uncategorized — Tags: , , , — James @ 3:19 pm

Apple’s Mac OS X provides built-in support for SOAP web services out of the box in a “check-mark in the feature table” sense. Sadly, the API isn’t up to the usual standards that one is accustomed to from Apple. It’s all in Carbon for one thing (so it’s in C and can’t take advantage of Leopard’s new memory management). It’s also incredibly limited and fairly buggy. And the documentation is sparse at best. Still, I’ve been stubbornly making an effort in the past couple of weeks to figure it out and use it in interesting ways. I decided to write up a little bit in the hopes that Google will archive it and it will help someone out. Specifically, these are my experiences with calling a SOAP service provided by a Microsoft .Net application (running on IIS). I don’t know how much of my problem is just because Apple and Microsoft disagree on the SOAP standards, or because Apple’s implementation is just buggy. So take all of this with a Microsoft-sized grain of salt.

Apple provides a utility, WSMakeStubs, which claims to parse WSDL files and generate appropriate stub classes in several languages. I’ve seen some evidence that it works fine with simple services that only use strings and integers; but the service I’m interested in makes heavy use of custom types (which are themselves subclasses of custom types), arrays filled with these types, types filled with arrays and all sorts of other fun things. When I pointed it at my WSDL, WSMakeStubs just filled my classes with comments that basically said “I have no idea what’s going on.”

So, for me, WSMakeStubs was a non-starter. Not to be deterred, I started experimenting with the WSMethodInvocation API. The API revolves around a WSMethodInvocationRef Carbon object (so it follows the typical CFRetain/CFRelease Create Rule).

The best documentation I’ve found on the API is really just some annoted example code from Apple. It actually does a pretty good job of walking you through the steps required to call a simple service. It did not, however, work for me.

For example, the line:

NSDictionary *reqHeaders = [NSDictionary dictionaryWithObject:@"" forKey:@"SOAPAction"];

is wrong. I found that I needed to do something closer to:

NSString *serviceNameSpace = @"ServiceNameSpace.ProvidedBy.TheWSDL";
NSString *requestName = @"Ping";
NSString *soapAction = [NSString stringWithFormat:@"%@/%@", serviceNameSpace, requestName];
NSDictionary *reqHeaders = [NSDictionary dictionaryWithObject:soapAction forKey:@"SOAPAction"];

or else IIS will freak out.

Another important difference is in the WSMethodInvocationCreate() call. For the service I’m using, I have to append “Request” to my method name. So in the SOAPAction header, it’s “Ping”, but when I create the WSMethodInvocationRef, I have to remember to use “PingRequest”. This is probably dependent on the service you’re calling, though. Check your WSDL for details.

Take a look at the WSMethodInvocation — Result Dictionary section of the docs. The best you can say about it is that it uses an economy of words rarely seen today (as a counterpoint, you may look at this blog post). From this documentation, you might believe that the CFDictionaryRef/NSDictionary* result from WSMethodInvocationInvoke will contain a kWSMethodInvocationResult key. You would be wrong. Based on random mailing list posts, I’ve concluded that it works correctly for XML-RPC requests but that Apple forgot to add it in for SOAP requests.

The only way I’ve discovered to actually get back the SOAP result for a method call is to force the result to include the incoming body debug headers with

WSMethodInvocationSetProperty(soapReq, kWSDebugIncomingBody, kCFBooleanTrue);

which you can then fish out of the result object with the key “/WSDebugInBody”. Feed those into an NSXMLDocument and you can parse out your results the old fashioned way. Again, see your WSDL for details.

Next time, I’ll spend way too much time talking about the best solution I’ve found for serializing types more complicated than a CFStringRef. I bet you can’t wait!

Blog at WordPress.com.