Friday, April 18, 2008

Web Services Fun

Before we work more with libxml, let's talk about web services. Web services are a good practical application of XML and, it would seem to me, many iPhone apps will need to work as web service clients, so it seems like a good detour.

The easiest way to access web services from Objective-C is by leveraging functionality found in Core Services, a framework that is available in both Cocoa and Cocoa Touch. For both platforms it needs to be manually linked into your project as it is not included by default in the Xcode templates.

For a Cocoa project, simply select Project -> Add to Project... then add the framework found at the following location to your project, making sure to uncheck the "copy into your project folder" option:
/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/CoreServices.framework
For the iPhone, well, you'll need to figure that one yourself; I've already gotten in trouble once with Apple for telling people where it is, but if you use Spotlight, you should be able to find it without too much difficulty.

Now once you've got the Core Services framework included in your project, how do you use it? Well, if the web service you want to access has a WSDL then it's really quite easy. As part of the Developer tool install, a little command line utility was installed on your machine called WSMakeStubs which will generate stubs with all the methods you need to access that web service. For a Cocoa project, the way to use it is as follows:
WSMakeStubs -x ObjC -name MyClassFileName -url http://server:port/path/to/wsdl
So, let's say that we wanted to create stubs for accessing the National Weather Service. For that, you would type this:
WSMakeStubs -x ObjC -name WeatherService -url http://www.weather.gov/forecasts/xml/DWMLgen/wsdl/ndfdXML.wsdl
Once you issue that command, it will create four files for you:
  • WeatherService.m
  • WeatherService.h
  • WSGeneratedObj.m
  • WSGeneratedObj.h
The first two implement the class you'll actually use to interact with the web service, the second two are generic classes that you need in your project, but which will be generated every time you run WSMakeStubs. If you run WSMakeStubs again, you can just delete the new versions, as they will be exactly the same as the ones you already have.

No, go ahead and add these four files to any Xcode project where you want to access the web service. If you're playing along at home, I just created a Foundation Tool Xcode project and added the four files to it. We need to import the WeatherService.h file wherever we intend to use it, like so:
#import "WeatherService.h"
In order to use this service, we need to know what functions this web service exposes to us. So, how do we know what methods we can call? Well, you can look at the WSDL or documentation that goes along with the web service if those are available. If not you can simply open up WeatherService.h and scroll down toward the bottom. The last @interface declaration in the file — ndfdXMLService — is the class we'll be working with. You'll notice that it has several class methods which you (of course) remember are the methods that begin with a + rather than a -.  These class methods correspond to the functions exposed in the web service. Let's pick one to try out, shall we? How about:
+ (id) LatLonListZipCode:(CFTypeRef /* Complex type http://www.weather.gov/forecasts/xml/DWMLgen/schema/DWML.xsd|zipCodeListType */) in_zipCodeList;
This is the simplest one in the web service, so that makes it good for showing the basics of accessing a web service. Now, whenever possible, WSMakeStubs uses known datatypes when constructing method calls. Sometimes, however, you'll see parameter specified as CFTypeRef, which is similar to id in that it's a generic pointer whose type is not specified. That means either that that parameter is more complex than a single datatype can hold OR that WSMakeStubs was unable to determine which datatype or object would be best for holding that parameter. Since it knows it doesn't know everything, WSMakeStubs includes a comment with a link to the XSD file where the parameter is defined to help you out. 

The problem is, it's not obvious nor well-documented how to use web service methods that take CFTypeRef parameters. It's easy enough to figure out how to pass in a raw datatype, but not so easy otherwise.

Now, unlike this particular one, a great many web services will take only simple parameters and will be relatively easy to use. Here is an example of how you might call such a web service function:
id result = [myWebService callMethodThatTakesInt:1];
The value of result will either be an NSString or an NSData instance depending on the web service. Ordinarily, the result will contain XML data with the web services response to your call.

But what do we do in this case since we have a complex type? Well, unfortunately, we have to read the documentation and poke around the stubs and experiment a little. In many cases, complex objects expect an NSDictionary with the xml tag name used as the key and the tag's contents as the corresponding object. For complex types where there are nested tags, some of the objects in the NSDictionary you create might themselves be NSDictionary instances. Because of this, building your parameter objecs can get fairly complex in some instances. 

Fortunately for us, it turns out that this web service function just takes a single string containing a space-separated list of zip codes. Well, we can do that. Heck, we can do that in one line of code:
id result = [ndfdXMLService LatLonListZipCode:@"13413 94588 12901"]; 
Notice that we are using NSString, and not c strings here. You could also use CFString because they are toll-free bridged to NSString but since we're in Cocoa, let's stick with Cocoa, shall we?

After running this code, result will be a string that contains XML with tags representing the longitude and latitude for each of the zip codes passed in. Blogger won't let me paste that XML in - it keeps thinking the XML is HTML and if I change the tags to use > and < it helpfully converts those back to angle brackets. *sigh* Well, the price is right. Anyway, you can see the XML here if you want.


Now, we have the server's response in XML... if only we had an easy way to parse XML in Cocoa and Cocoa Touch. Oh, wait... I, um...  promised you something vaguely similar to that yesterday, didn't I? :)

Coming next: The libxml wrapper and a little practical application of it.

No comments:

Post a Comment