Thoughts from a NeXTStep Guy on Cocoa Development

Custom Views During Text Entry

Oct 05, 2009 by Bill Dudney

The app I'm currently working on requires a custom view to be taking all the visible are between the search bar and the keyboard. So I spent a bit of time experimenting and came up with this little recipe that might be useful to you.

The first thing you need to do is register to be notified when the keyboard becomes active. This code makes that happen.

  [[NSNotificationCenter defaultCenter] addObserver:self

I placed this code into my view controller's viewDidLoad method but a different location might be more appropriate for you.

Then in my keyboardWillShow: method I calculate the visible space and make that the frame of my overlay view. Like this;

- (void)keyboardWillShow:(NSNotification *)notification {
  if(nil != self.view.window){
    CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
    CGRect keyboardBounds = [[[notification userInfo] valueForKey:UIKeyboardBoundsUserInfoKey] CGRectValue];
    CGRect searchBounds = [self.searchBar bounds];
    CGFloat height = CGRectGetHeight(appFrame) - CGRectGetHeight(keyboardBounds) - CGRectGetHeight(searchBounds);
    CGRect visibleBounds = CGRectMake(CGRectGetMinX(appFrame), 
                                      CGRectGetMinY(appFrame) + CGRectGetHeight(searchBounds),
    self.overlay.frame = visibleBounds;

This works for me because my overlay view becomes a subview of view controller's view. If your view hierarchy is different you might need to do some different math.

Now with this view in place I can process a single tap as a cancel. You could do just about anything here though.

Happy coding!

Permalink     3 Comments - Add Yours

iPhone SDK Development In Print (W00T!)

Oct 01, 2009 by Bill Dudney

If you follow me on twitter you've already hear. But I'm still so excited that I can't keep from posting here. The iPhone SDK book is finally in print. Yippy!

Permalink     1 Comment - Add Yours

360iDev Denver 2009 MapKit Presentation

Sep 30, 2009 by Bill Dudney

The presentation I gave today at 360iDev is now online here;

The sample code is also available.

Permalink     2 Comments - Add Yours

Inversion of Control

Sep 11, 2009 by Bill Dudney

I've been in the Cocoa/CocoaTouch world now full time now for more than two years. But I still remember bits and pieces of my former life in the Java IT world. One of the hot topics there (or it was hot 2 years ago) was Spring.

Spring was such a big thing because it relieved Java programmers from the tedious complex nature of J2EE programming and made it fun and simple again to write apps. The main idea behind Spring was coined 'Inversion of Control'. The term came from the fact that the controller class you were writing did not need to know where its JDBC (database stuff) connection came from, instead it just relied on the connection being set by someone else. In other words the controller gave up control of knowing everything about its dependent objects at all times for a much simpler programming model.

It struck me during the last studio that Daniel and I were teaching that Interface Builder and the loading mechanism for nib files is a lot like the Spring module loading stuff. Sure you give up control and knowing everything all the time, but what you get is priceless.

One example is the files owner. I don't remember the exact term used in Spring but you can declare a bean in a spring config file that says 'I represent an object that will be supplied at load time' and then proceed to add configuration for that bean despite the fact that the object is created elsewhere. Before this file is loaded none of the configuration represented there has been performed, despite the object being created. When you need the configuration, you load the spring file and bada-boom, bada-bing you have your configuration.

In IB you create a nib file and set the file's owner to some class of your making. You connect the view, wire up some actions to buttons or other controls etc. But none of that configuration is available until the nib file is loaded. Consider a nib file whose file's owner is one of your view controllers, you create the instance of the VC in a parent nib file. When that nib is loaded the VC is created, but it does not have a view or the target/action connections specified in the child nib file. However when it's asked for it's view and the nib file is loaded all those connections are made.

The much simplified bunch of code that you get is one thing, but even better is that you get a simplified programming model. True that much of the simplified programming model comes from the way that Cocoa Touch works. But, most of the design patterns used in Cocoa Touch are influenced by IB (and vice-versa).

My hope is that if you are coming from Java and you are familiar with Spring that this write-up will help you to get a little further along in your understanding of IB and how it works. Just like spring folks would never dream of writing an IT app in the old style (J2EE 2.0 old style) because of its complexity, I would never dream of writing an iPhone or Mac app without using IB, why would I poke myself in the forehead with a sharp stick :)

Give up your need for control and be happy!

Permalink     3 Comments - Add Yours

Core Data Code Generation

Sep 08, 2009 by Bill Dudney

When building Core Data applications you can choose to use the NSManagedObject class for most of your entities, but from time to time you need to have a custom class to add custom code to. You might want to do lazy initialization of transient attributes, validation of the instance, or any number of other things you need to do with instances of your Core Data classes (they are Objective-C objects after all).

When you start adding custom code to your classes maintenance becomes a hassle, when you add an attribute you can either manually keep your custom classes in sync or regenerate the code. When you regenerate Xcode kindly keeps the old version of your code (if you click the right button) but you have to manually merge your custom code back into the freshly generated code. The manual merge is almost always a piece of cake but it can still be a time sink to have to do that with each new attribute you add. So I use categories to manage my custom code and make the regeneration of classes a snap.

Categories, for those that don't know, are a way to extend existing classes in the Objective-C runtime. A category can be thought of as a bag of methods that are added to a class when the category is loaded (of course the real technical details are a bit more complex). So I put all my custom code into a category that I typically call 'Custom' in a separate files named ClassName+Custom.h and ClassName+Custom.m. That way when the main class is regenerated I don't have to manually merge the custom code back into my generated code.

Here is the interface for a Movie class that we could persist via Core Data.

@interface Movie : NSManagedObject {

@property(nonatomic, retain)NSString *name;


And here is the 'Custom' category interface for the Movie class.

#import "Movie.h"

@interface Movie (Custom)
- (void)play;

What this is saying to the compiler is that we have a new method 'play' that will be added to the Movie class at runtime when this category is loaded. If you import Movie+Custom.h instead of Movie.h you will be able to use this play method without compiler warnings.

The implementation of the Movie class is simple, its just got the @dynamic statement in the implementation block. The category implementation looks like this.

#import "Movie+Custom.h"

@implementation Movie (Custom)

- (void)play {
  NSLog(@"playing %@",;


This implementation block provides the code for the play method and is what gets added to the Movie class at runtime.

You have to, of course, be careful with Categories because you can get into trouble. For example, what happens if you replace a method on the main class with a method in the category? I'll leave that between you and the docs. But don't be afraid of this sort of straightforward usage of categories, it can make your life much easier.

Hope this helps your usage of Core Data to be even better.

Permalink     2 Comments - Add Yours

libxml2 Push Parsing

Sep 03, 2009 by Bill Dudney

The fastest way to parse XML on iPhone OS is with libxml2. You can find out all about libxml2 from the official site here. There is a ton of functionality and we won't have time to go over all of it here, so you know where to go for the full details. In this article we are going to show how to use libxml2 in the real world of iPhone development instead of delve into its huge feature set.

Generally there are two approaches to parsing XML. The first is often referred to as DOM based parsing. In this approach the whole XML document is loaded into memory and an object-oriented hierarchical representation is built in memory. This is a great intuitive approach to using XML, but as you can imagine it can be hugely resource intensive. If you have a massive amount of XML it can sometimes be very difficult to use a DOM based parser because you can consume all your available memory. That is of course bad...

The second approach is called SAX parsing. This family of parsers uses an event model. Every time the parser finds something interesting in the XML (such as an element or set of characters) an 'event' happens that invokes a callback into your code so you can do what you will with that event. The advantage of this approach is that you can parse tons of XML and not run out of memory because the parser only keeps in memory a fraction of the entire XML document at once. The disadvantage is in writing the event call backs. It can be difficult to build the data structures you need to get the information you want from the XML. For example two elements in an RSS feed have a subelement called 'title'. In order to parse an RSS feed properly your event call back needs to know if its processing the item's title or the channel's title. So you have to keep track of what elements have been found and in what order so you know which title you have.

Which approach you take depends on your app, specifically how much XML you have to process. TouchXML is a great DOM based parser. The NSXMLParser class is the Foundation class for doing SAX based parsing. If you want to strictly stay in the ObjC world and not have to think about function pointers and the like then TouchXML or NSXMLParser are for you. And finally you have libxml2 as an alternative SAX parser. libxml2 is a lot less memory and processor intensive compared to either of the alternatives. Both TouchXML and NSXMLParser are built on top of libxml2 and both are optimized for their particular uses, i.e. please don't think I'm saying in this article not to use either of these alternatives. They are great for what they are meant to do. In this article I want you to see how to use libxml2 if your app uses tons of XML and needs to be able to parse it faster or with less memory usage.

To demonstrate the use of libxml2 I'm going to modify the EarthquakeParser I built for my Map Kit Screencast. In the example I parse an XML feed from the USGS to find earthquakes in the vicinity of the user and then plot them on the map. The XML parsing is not the focus of the screencast so I used NSXMLParser. Even though the application does not need a faster XML parsing it makes a good example to add libxml2 too. I'll leave the map part of the example to the screencast and just put the earthquakes into a table view. You might recognize this as an example from the ADC and while the display is similar the XML parsing will be totally different. You can look at the feed yourself here, you will likely have to 'view source' once navigating to that URL to see the XML.

Now that we have the intro out of the way lets get started by pulling the XML via an NSURLConnection. The NSURLConnection class is a beautiful expression of the simplicity of Cocoa/Foundation API's and design. Through a really simple API we get concurrent (i.e. threaded) downloading of files. It really is amazing how simple it is to get data from a url with this class. The official docs are here. I will summarize though.

When you start an NSURLConnection it creates a background thread to pull the data from the URL you provided in the NSURLRequest. When an interesting thing happens (like some data is received) the connection packages up the data and pushes a call to the delegate onto the main event thread via code that looks more or less like this;

    SEL selector = @selector(connection:didReceiveData:);
    NSMethodSignature *sig = [(id)self.delegate methodSignatureForSelector:selector];
    if(nil != sig && [self.delegate respondsToSelector:selector]) {
      NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
      [invocation retainArguments];
      [invocation setTarget:self.delegate];
      [invocation setSelector:selector];
      [invocation setArgument:&self atIndex:2];
      [invocation setArgument:&receivedData atIndex:3];
      [invocation performSelectorOnMainThread:@selector(invoke) withObject:NULL

I don't have access to Apple's source code, this is just informed speculation...

In this way you don't have to manage any threading state or locks or whatever. The data comes to you via the main event loop so you don't even have to be aware of the alternate thread. Here is a graphical representation of what is going on for the visual learners out there;

For each delegate method the connection invokes the same basic process happen. For example when an authentication challenge is issued by the server, the connection packages that up as a method invocation on the main thread. Your response is then captured and acted on when you return from the delegate method. This pattern is worth studying it makes so many things in Cocoa/CocoaTouch much simpler than they would otherwise be. It's worth your time to study it in depth.

For our example we are going to use the connection to pull our earthquake feed from the USGS. Which in this case leads us to the libxml2 parsing code. Lets start with setting up the connection.

  NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURLString]]
  NSURLConnection *con = [[NSURLConnection alloc] 
  self.connection = con;
  [con release];
  if(self.connection != nil) {
    do {
      [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode 
                                                         beforeDate:[NSDate distantFuture]];
    } while (!_done && !self.error);

We have to run the current run loop so that the connection will get messages about the underlying CoreFoundation stuff it uses to download the data. There is a run loop for every thread in your process which you can get by asking the NSRunLoop for it.

Next, here is the delegate method impl that the connection calls when data is received.

- (void)connection:(NSURLConnection *)connection 
    didReceiveData:(NSData *)data {
  // Process the downloaded chunk of data.
  xmlParseChunk(_xmlParserContext, (const char *)[data bytes], [data length], 0);

For each chunk of data that comes in we pass it off to the libxml2 function xmlParseChunk. We will look at creating the xmlParserContext in just a second for now look at how simple this is. All we do is pass the bytes from the data off to the libxml2 parser context and we are done. Nice and easy, beautiful. When the connection finishes downloading all the XML it sends us this message;

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  // Signal the context that parsing is complete by passing "1" as the last parameter.
  xmlParseChunk(_xmlParserContext, NULL, 0, 1);
  _done = YES;

And we pass a NULL into the xmlParseChunck function to let libxml2 know that there is no more XML.

Now that we see how the connection invokes the xml parser code lets look at how we setup the xml parser.

  _xmlParserContext = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);

Deceptively simple, all we do is call a single function to get libxml2 setup to parse our XML. The first argument is a pointer to a structure full of function pointers. This is how C based programs do callback's. Remember that SAX parsers are event based, so each significant event (like an element being found) is sent to a callback function. We pass in self as the 'context' pointer. The context is passed into each of the callback functions. We see in just a minute why that is important. In our case the list of functions looks like this;

	static xmlSAXHandler simpleSAXHandlerStruct = {
	  NULL,                       /* internalSubset */
	  NULL,                       /* isStandalone   */
	  NULL,                       /* hasInternalSubset */
	  NULL,                       /* hasExternalSubset */
	  NULL,                       /* resolveEntity */
	  NULL,                       /* getEntity */
	  NULL,                       /* entityDecl */
	  NULL,                       /* notationDecl */
	  NULL,                       /* attributeDecl */
	  NULL,                       /* elementDecl */
	  NULL,                       /* unparsedEntityDecl */
	  NULL,                       /* setDocumentLocator */
	  NULL,                       /* startDocument */
	  endDocumentSAX,             /* endDocument */
	  NULL,                       /* startElement*/
	  NULL,                       /* endElement */
	  NULL,                       /* reference */
	  charactersFoundSAX,         /* characters */
	  NULL,                       /* ignorableWhitespace */
	  NULL,                       /* processingInstruction */
	  NULL,                       /* comment */
	  NULL,                       /* warning */
	  errorEncounteredSAX,        /* error */
	  NULL,                       /* fatalError //: unused error() get all the errors */
	  NULL,                       /* getParameterEntity */
	  NULL,                       /* cdataBlock */
	  NULL,                       /* externalSubset */
	  XML_SAX2_MAGIC,             // initialized? not sure what it means just do it
	  NULL,                       // private
	  startElementSAX,            /* startElementNs */
	  endElementSAX,              /* endElementNs */
	  NULL,                       /* serror */

That looks like a lot but really its just placing the function pointers (endDocumentSAX for example) into the correct slot in the xmlSAXHandler structure so that libxml2 knows which function to call for each event. So when an element is found the startElementSAX function will be invoked. The function declaration (which arguments etc) are all defined and documented on the libxml2 website. In don't have time to look at each of these functions (the example code has all this laid out, look there for the rest of them) so we will just take a look at one, the startElementSAX function.

static void startElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix,
                            const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces,
                            int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
  [((EarthquakeParser *)ctx) elementFound:localname prefix:prefix uri:URI 
                       namespaceCount:nb_namespaces namespaces:namespaces
                       attributeCount:nb_attributes defaultAttributeCount:nb_defaulted

Here we see why having the context defined as an ObjC object is a 'good thing'. Since the context is our EarthquakeParser we can invoke methods on this object and thus move from the C world back into the comfortable ObjC world. The implementation of the elementFound:prefix:uri:namespaceCount:namespaces:attributeCount:defaultAttributeCount:attributes: is too long to place here but a couple of interesting things to note.

The xmlSAX2Attributes structure is a type of my own (that I found through googling) that makes it easier to process the attributes. The attributes are sent to the startElementSAX function as a list of lists. The second dimension of the array is always 5 long so I made a struct that makes it easier to decode.

struct _xmlSAX2Attributes {
  const xmlChar* localname;
  const xmlChar* prefix;
  const xmlChar* uri;
  const xmlChar* value;
  const xmlChar* end;
typedef struct _xmlSAX2Attributes xmlSAX2Attributes;

The other interesting thing about the attributes is that the value is not NULL terminated which means you have to do some pointer arithmetic to figure out how long the value really is. We will see that in a moment. Now that we know about the attributes let's look at how to use the elementFound... method to get data out of our XML.

First for each element that I'm interested in the XML I define two constants like this.

	static const char *kEntryElementName = "entry";
	static NSUInteger kEntryElementNameLength = 6;

These two constants give us a constant to compare the element name against using strncmp which is faster that its cousin strcmp. In this method I set up a if/else if/else block, one if or else if for each element I want to process. The code looks like this.

if(0 == strncmp((const char *)localname, kEntryElementName, kEntryElementNameLength)) {
  self.currentEarthquake = [[[Earthquake alloc] init] autorelease];
} else if(0 == strncmp((const char *)localname, kLinkElementName, kLinkElementNameLength)) {
  for(int i = 0;i < attributeCount;i++) {
    if(0 == strncmp((const char*)attributes[i].localname, kRelAttributeName,
                    kRelAttributeNameLength)) {
      // process the rel attribute
	} else if(...) {
      // process a different attribute
} else if(0 == strncmp((const char *)localname, kTitleElementName, kTitleElementNameLength)) {
  _title = [[NSMutableString alloc] init];
  _parsingTitle = YES;

In this code we process each interesting element and each interesting attribute from each of the elements. Of course there is a lot of detail on how these elements and attributes are processed, but that is mostly application specific. For this example there are a couple of elements (rel for example) that have attributes that need to be processed. For other elements (like title) we only care about the text contained in the element. Your XML schema and the data you need from instances of that schema will dictate what kind of processing you need to do here. When you do care about the content of the element you can process it as we do here. Turn on a flag that says you are parsing the title and make a mutable string to hold the found characters. Then in the libxml2 callback invoke another method on your parser like this.

static void	charactersFoundSAX(void *ctx, const xmlChar *ch, int len) {
  [((EarthquakeParser *)ctx) charactersFound:ch length:len];

The parser then processed the characters like this.

- (void)charactersFound:(const xmlChar *)characters length:(int)length {
  if(_parsingTitle) {
    NSString *value = [[NSString alloc] initWithBytes:(const void *)characters
                                               length:length encoding:NSUTF8StringEncoding];
    [_title appendString:value];
    [value release];

Then you can capture the characters you found in your XML via the end of element function like this.

static void endElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix,
                          const xmlChar *URI) {    
  [((EarthquakeParser *)ctx) endElement:localname prefix:prefix uri:URI];

The parser processes the end of the title element like this.

- (void)endElement:(const xmlChar *)localname
            prefix:(const xmlChar *)prefix
               uri:(const xmlChar *)URI {
  if(0 == strncmp((const char *)localname, kTitleElementName, kTitleElementNameLength)) {
    //M 5.8, Banda Sea
    NSArray *components = [_title componentsSeparatedByString:@","];
    if(components.count > 1) {
      // strip the M
      NSString *magString = [[[components objectAtIndex:0] 
                               componentsSeparatedByString:@" "] objectAtIndex:1];
      NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
      self.currentEarthquake.magnitude = [formatter numberFromString:magString]; = [components objectAtIndex:1];
      [formatter release];
    [_title release];
    _title = nil;
    _parsingTitle = NO;
  } else if(0 == strncmp((const char *)localname, kUpdatedElementName,
                         kUpdatedElementNameLength)) {

So that pretty much wraps up how I use libxml2. I hope its useful to you. Before we part though I wanted to show you an ObjectAlloc trace from Instruments.

As you can see from this graph the NSXMLParser based solution takes more memory and more time. Of course you get a bunch for going this route, the NSXMLParser protects you from C function pointers and xmlChar pointers to pointers. If you aren't quite ready to take the plunge into the intricacies of C feel free to stay with the NSXMLParser.

You can grab the sample project here.

Happy Parsing!

[UPDATE] There was a syntax error (thanks for pointing it out Shogo) in the upload code bundle, so if you've downloaded and can't get it to work please grab the bundle again.

Permalink     9 Comments - Add Yours

Foretelling the iPhone

Aug 19, 2009 by Bill Dudney

I am currently reading The Design of Everyday Things. It is a great book, although it has often made me angry at the designers of doors and stove tops :)

In the section about memory Norman discusses the importance of reminders. There is some clever stuff in this chapter. He makes this statement.

Would you like a pocket-size device that reminded you of each appointment and daily event? I would. I am waiting for the day when portable computers become small enough that I can keep one with me at all times. I will definitely put all my reminding burdens upon it. It has to be small. It has to be convenient to use. And it has to be relatively powerful, at least by today's standards. It has to have a full, standard typewriter keyboard and a reasonably large display. It needs good graphics, because that makes a tremendous difference in usability, and a lot of memory - a huge amount, actually. And it should be easy to hook up to the telephone; I need to connect it to my home and laboratory computers. Of course, it should be relatively inexpensive.

I read that and thought wow this guy new the iPhone back in 1988 (first edition of the book). But, instead of having to hook it up to the phone, it is the phone.

Permalink     2 Comments - Add Yours

Mac Break Dev

Aug 17, 2009 by Bill Dudney

At WWDC I had the great opportunity to record several episodes of MacBreak Dev (iTunes Link). I really enjoyed it as you can see from this silly frame capture during one of the shots.

There is lots of great content in that pod cast. The three of mine that have been published so far are;

I'm looking forward to the remainder of the recorded episodes being published and hopefully in the future doing a few more. That was a blast!

Permalink     2 Comments - Add Yours

Map Kit Interview

Aug 17, 2009 by Bill Dudney

Last week I had a great time with Dan over at Mobile Orchard recording a pod cast about Map Kit. Find the text and download the pod cast here.

Permalink     Add A Comment

iPhone 3.0 SDK Screencast on Map Kit Shipping

Jun 30, 2009 by Bill Dudney

Just finished up the Map Kit screencast.

Permalink     2 Comments - Add Yours

Next iPhone Studio Class Announced

Jun 17, 2009 by Bill Dudney

As with the book now that Apple shipped the iPhone 3.0 update the NDA is officially gone and the iPhone Studio can officially be 3.0 aware. In celebration we also announced the next iPhone Studio for Aug 25 to 28 in Denver.

Would love to see you there!

Permalink     2 Comments - Add Yours

iPhone 3.0 shipping, that means the book is good to go!

Jun 17, 2009 by Bill Dudney

With Apple shipping the iPhone 3.0 update the NDA is officially gone and we were able to ship an updated copy of the book. There are a ton of changes for 3.0 but here are some of the highlights.

  • Core Data
  • Map Kit
  • iPod Library Access
  • AFFoundation
  • Core Audio
  • Updates throughout for 3.0 API changes

The book's page over at has some new excerpts too in case you are still on the fence about purchasing you can see a bunch of the new stuff in these new excerpts.

We are only a few weeks (6 to 8) away from the dead tree version now, boy I can't wait!

Permalink     3 Comments - Add Yours

Open GL ES 2.0 Resources

Jun 10, 2009 by Bill Dudney

With the announcement of the iPhone 3Gs came the availability (well at least when the hardware ships) of a programmable Open GL pipeline. Basically that means that just about everything changes for the new device. Not only is the new pipeline going to make for much faster apps, but its going to open up the door to tons of very cool stuff. For example Core Image (the tech behind the cool ripple transition in Dashboard) requires a programmable pipeline. No word on Core Image coming to an iPhone SDK near you but you don't have to be a clairvoyant prognosticator to see that coming.

Since everything out side the keynote is under the NDA (but only lasts till June 17) I can't really talk about the amazingly cool demos they have been showing on the new hardware. However here are some decent resources on Open GL ES 2.0 to start to get your mind wrapped around the new stuff.

Khronos spec page.

PowerVR's site.

GLSL spec (PDF).

I read Open GL ES 2.0 but I can't recommend it as a starting point. If you don't know Open GL already its a bit rough as a starting point. If you already know Open GL ES 1.1 then its a decent book.

If you are brand new to the whole thing you can read Jeff LaMarche's excellent series on OpenGL ES 1.1.

Permalink     Add A Comment

WWDC Keynote - iPhone SDK Book Changes

Jun 09, 2009 by Bill Dudney

New laptops were cool yesterday but the rest of the content was stuff we already knew about. Until the new iPhone 3Gs announcement.

Looking at the announcement the most exciting thing for me was OpenGL ES 2.0. I'm so stoked about that I can hardly wait to get my new device.

On the book front there were only a couple of small things to add. The Compass needs a couple of pages as well as the new video integration. Apart from that the book is good to go so we are very very close to finishing up and getting the book into the final bits of work (indexing and other technical book stuff).

Permalink     Add A Comment

WWDC Keynote Line

Jun 08, 2009 by Bill Dudney

Having a great time in line for WWDC09. I know its crazy to be up at 4:30 am to watch a keynote, but...

Here is the line in front of me.

And the line behind me.

Permalink     Add A Comment