Pretty image
Alexander shares his experience in developing an iPhone app—and shares the code, too.

Being a programmer and having an iPhone and a Mac but not trying to write a mobile app was a little embarrassing for me. Eventually I decided to fix it and dived into Objective-C and Cocoa.

Luckily I had come across a problem that nicely fitted the requirements for a first iOS app. The problem was real, useful, but also not very complicated.

Disclaimer: this is not only my first iOS application. It is even worse. This is my first application in Objective-C ever. I don’t claim in any way that the implementation is efficient or elegant. But I hope that at least it is a quite simple, clean, and complete example of what a programmer writing a first iApp will run into. If you find any issues with the code, please let me know.

Houston, We Have a Problem!

Last year I applied a few times for a US visa in London. Unfortunately, each time my application required what they called “administrative processing.” After your application is formally accepted, you are given a batch number. At the embassy website there is a PDF file with statuses of visa applications currently in “administrative processing.” The file is updated regularly, and you have to periodically check your status there to know the next step (provide more details, send a passport, etc.) You click on a link, the browser brings up the PDF file, and you search for your batch number to find the update.

I came up with the idea to automate this routine. I realized it could be an iPhone app where I can put in my batch number once and then retrieve my current status by only pressing a refresh button. Or maybe the app could check the status in the background and notify me when it gets changed.

I should clarify that this is not just a Macintosh coding example. If you’re stuck with Windows, there is some relief. Objective-C is available on Windows with Cygwin or MinGW. Moreover, there is a GNUstep project bringing AppKit and Foundation libraries to Windows. With GNUstep you develop a fullblown UI application for Windows. But I’m not going to dig that deeply, and we will only create a command-line application capable of downloading the PDF file and parsing it. The application will work on both OSX and Windows (via GNUstep). But the rest of the application, its UI, of course, will be only relevant for Mac users.

GNUstep Prerequisites for Windows Users

I found a couple of extremely useful blog posts:

  • Learn Objective-C on Windows, explaining how to install GNUstep and try a trivial application.

  • Clang and Objective-C on Windows, explaining how to build the latest Clang compiler for your GNUstep environment. Unfortunately, the GCC compiler bundled within the GNUstep packages does not support Objective-C features required by Apple. Also, there is no official Clang installer for Windows. That is why you have to build it by yourself. I followed the recommendations from this blog post, and it all worked for me.

Getting Familiar with Objective-C and the iOS API

I had no Objective-C and Cocoa experience whatsoever. I also heard that Objective-C is quite the strange beast compared to C++ and Java, so I started at the very beginning and thumbed through a few books:

  1. iOS Programming: The Big Nerd Ranch Guide, 3/e

  2. Objective-C Programming: The Big Nerd Ranch Guide

  3. Programming in Objective-C (4th Edition)

Plus one more neat document called “From C++ to Objective-C”.

I split the application into three main parts:

  • PDF parser

  • PDF downloader (without sticking to a particular UI implementation)

  • UI

After getting into Objective-C, I would say that if you are an experienced C/C++ developer and familiar with basic concepts of creating UI (I personally spent years programming in Delphi and C++Builder), Objective-C and Cocoa are not really difficult to understand. I would only recommend focusing on Objective-C memory management, because after C++ with RAII or Java with the garbage collector, you may find memory management in Objective-C a bit awkward. You have to manually control allocating and freeing memory as in C, and the only mechanism provided by the runtime for you is reference counting. It is better that a purely manual approach as in C, but it’s still tough to do it right. You must precisely follow Objective-C memory management guidelines and naming conventions for constructors. Otherwise you will inevitably end up with memory leaks. This is exactly what happened to me in the very first version of the application. The good news is that the Xcode code profiler does an amazing job helping to identify memory leaks.

I will describe a few of my observations about Objective-C and Cocoa. I have found them quite interesting from the C/C++ developer’s point of view. A few pieces of code from the project will help demonstrate them. I’m not going to explain every single line though. Instead, I’d like you to get the taste of Objective-C, if you are not familiar with this language at all.

To begin with, let’s have a look at how Objective-C names class methods. It looks almost like a natural language. For example, if I say “please, find a needle in a portion of some data and add the result to a list implemented as a mutable array,” in Objective-C it will be:

 + (bool)findInPortion:(NSMutableData *)someData
  needle:(NSString*)aNeedle
  andAddTo:(NSMutableArray*)aList {
  ...
 }

If you read this code from top to bottom, left to right, it sounds almost like the original English sentence. Formally, the signature of this method is findInPortion:needle:andAddTo:. The parameters have names, and the names are part of the method signature. By choosing good names for the parameter variables (someData, aNeedle and aList) you can form almost natural sentences. Agreed, it is wordy, but the incredible code prediction system in Xcode helps you type such long statements very easily. Also, it is worthwhile to point out a somewhat unusual way to break long statements down into multiple lines. In Objective-C they are aligned by the : symbol separating the argument name and the variable.

One more observation. Objective-C uses unusual syntax to call class methods. For example, instead of the quite common dot notation:

 NSMutableArray* list = NSMutableArray.alloc.init;

you write:

 NSMutableArray* list = [[NSMutableArray alloc] init];

It looks strange, but it is a question of habit. Again, the Xcode code prediction system helps put square brackets sometimes without even your typing them.

Objective-C and Cocoa actively use some design patterns. One of them is a delegate pattern. Delegates are everywhere in Cocoa. For example, I used the NSURLConnection class to download the PDF file. When you instantiate this class, you must provide a delegate, NSURLConnectionDelegate, whose methods allow hooking up into the process of downloading.

Eventually, after a couple of weeks of reading at evenings and experiments, and I was ready to sketch a skeleton of my iOS application. The next part of the Ballet de la Merlaison was about parsing the PDF format.

PDF Parsing

The document containing visa application statuses is a PDF file. Luckily, PDF manuals are available online on the Adobe website. I used a document called “PDF Reference third edition, Version 1.4”.

I came up with a quite straightforward implementation of the PDF parser. The parser routine is called each time when a chunk of data is received from the downloader. The parser tries to locate blocks of data marked by stream and endstream markers. Then such blocks are unzipped by zlib/inflate, and the parser checks whether your batch record appears in the chunk. If yes, it extracts the visa status update and adds it to a list (a batch number may have several updates in that PDF file). Finally, the parser cuts out the processed piece of data (between stream and endstream markers) from the buffer.

The algorithm:

  1. Find each block in the PDF data surrounded by the stream\r\n and endstream\r\n markers. The content of such blocks is packed using the zlib deflate algorithm. We inflate it.

  2. Each unzipped (inflated) block of data is a plain text with PDF markup tags. We need to locate blocks of the text embraced by "BT\r\n" (Begin Text) and "ET\r\n" (End Text) tags. All these blocks will be collected in a list of strings.

  3. Inside each block of text identified on the step 2 (elements of the collected list of strings) we need to find all substrings embraced by parentheses (( and ) characters). Then we join all these substrings, and the result text will be a plain text representation of the user information we are looking for. So on this step we will be processing each string found on the step 2 by removing all substrings that are not surrounded by ( and ).

  4. Finally we have to identify the structure of the plain text. The batch status information in our PDF is formatted as a three-column table. The columns are: batch number, status, and date. Unfortunately, the PDF also contains some extra text (headers, footers, etc.) which will also appear in our parsed plain text along with the useful information. We will identify the table column fields by iterating sequentially along the list of strings. If the current string looks like a batch number (11 decimal digits), we take it as the batch number, and the following two strings will be interpreted as the status update and the date accordingly. We take these three string and jump over them further to find the next potential batch number.

Of course, we pretty much hard code the format, and if clerks at the US embassy change the format, it may not work anymore. But at the moment these four steps work fine for parsing the existing PDF.

The command-line application

Now we have everything in place to build up a command-line application for downloading the PDF file, parsing it, and looking for a particular batch number. As I said, this application can be built natively on Mac using the latest Xcode or on Windows via GNUstep and the latest Clang compiler (3.2+). Importantly, the source files of this command line program will be used unchanged in the fully functional UI iOS application.

There are several files involved:

  • BatchPDFParser.m (and .h)—The PDF parser.

  • NSURLConnectionDirectDownload.m (and .h)—The downloader. This file contains plumbing around the NSURLConnection class (initialization, delegates, waiting loop).

  • DirectDownloadDelegate.m (and .h)—The delegate attached to NSURLConnection. This delegate class provides callbacks invoked by NSURLConnection notifying us about different events during downloading.

  • ViewController.m—The ViewController prototype. This file contains glue between the downloader and the UI. OSX and iOS use the MVC (Model-View-Controller) paradigm. So, the controller provides the link between visible UI components (buttons, text fields, check and radio boxes, etc.) and invisible ones driving the business logic. Because our batch parser and downloader will be the parts of the full-blown UI application, we prepare the prototype of the UI controller. For the command-line application it will contain a few stubs only. This is the only file that is slightly simplified compared to its full-blown version.

  • main-cli.m—the main application entry point.

I will list files one by one, putting commentaries along the code.

BatchPDFParser.h

This file contains declarations of the Batch object representing a single batch update and the BatchPDFParser providing the findInPortion:needle:andAddTo: method. Note, this method is static (see the + at the beginning of the line) and can be used without class instantiation.

 @interface Batch: NSObject {
  NSString *batchNumber, *status, *date;
 }
 @property (atomic, copy) NSString* batchNumber, *status, *date;
 @end
 
 @interface BatchPDFParser: NSObject
 
 + (bool)findInPortion:(NSMutableData *)data needle:(NSString* const)needle
  andAddTo:(NSMutableArray*)list;
 
 @end

BatchPDFParser.m

This file contains the implementation of the PDF parser.

 #import <Foundation/Foundation.h>
 #import "BatchPDFParser.h"
 #import "zlib.h"
 
 @implementation Batch
 @synthesize batchNumber, status, date;
 
 - (void) dealloc {
  [batchNumber release];
  [status release];
  [date release];
  [super dealloc];
 }
 @end
 
 @implementation BatchPDFParser

The findInData:fromOffset:needle: private method searches for a given string in a block of data (similar to strstr()).

 + (int) findInData:(NSMutableData *)data fromOffset:(size_t)offset needle:
  (char const * const)needle {
  int const needleSize = strlen(needle);
  char const* const bytes = [data mutableBytes];
  int const bytesLength = [data length] - needleSize;
  for (int i = 0; i < bytesLength;) {
  char const* const current = memchr(bytes + i, needle[0],
  bytesLength - i);
  if (current == NULL) return -1;
  if (memcmp(current, needle, needleSize) == 0)
  return current - bytes;
  i = current - bytes + 1;
  }
  return -1;
 }

The isBatchNumber:numberL methods checks whether a string represents a proper batch number:

 + (bool) isBatchNumber:(NSString*)number {
  long long const value = [number longLongValue];
  return value >= 20000000000L && value < 29000000000L;
 }

The findBatchNumberInChunk:needle:andAddToL method looks for a given string in a portion of the PDF file. It parses the BT and ET tags and contents between them embraced by parentheses. I don’t use regular expressions or any kind of grammars. It can be implemented better, but I leave it for readers as an exercise. My implementation is based on two simple state machines: the first iterates over BT and ET tags (the state variable) and cuts the contents surrounded by parentheses, and the second identifies situations when the batch number is extracted and takes the following two strings as the status and date.

 + (bool) findBatchNumberInChunk:(char const*)chunk needle:(NSString*)needle
  andAddTo:(NSMutableArray*)list {
  enum {
  waitBT, waitText, insideText
  } state = waitBT;
  enum {
  waitBatchNumber, waitStatus, waitDate
  } batchParserState = waitBatchNumber;
  NSMutableString* line = [[NSMutableString alloc] init];
  Batch* batch = nil;
  bool found = NO;
  while (*chunk) {
  if (state == waitBT) {
  if (chunk[0] == 'B' && chunk[1] == 'T') {
  state = waitText;
  [line deleteCharactersInRange:NSMakeRange(0,
  [line length])];
  }
  } else if (state == waitText) {
  if (chunk[0] == '(') {
  state = insideText;
  } else if (chunk[0] == 'E' && chunk[1] == 'T') {
  if (batchParserState == waitBatchNumber) {
  if ([self isBatchNumber:line]) {
  [batch autorelease];
  batch = [[Batch alloc] init];
  batch.batchNumber = line;
  batchParserState = waitStatus;
  }
  } else if (batchParserState == waitStatus) {
  batch.status = line;
  batchParserState = waitDate;
  } else if (batchParserState == waitDate) {
  batch.date = line;
  batchParserState = waitBatchNumber;
  if ([batch.batchNumber isEqualToString:needle]) {
  NSString* pair =
  [NSString stringWithFormat:@"%@\n%@",
  batch.status, batch.date];
  [list addObject:pair];
  NSLog(@"Found match: '%@' '%@' '%@'",
  batch.batchNumber, batch.status, batch.date);
  found = YES;
  }
  }
  [line autorelease];
  line = [[NSMutableString alloc] init];
  state = waitBT;
  }
  } else if (state == insideText) {
  if (chunk[0] == ')') {
  state = waitText;
  } else {
  char const c[2] = { chunk[0], 0 };
  [line appendString:[NSString stringWithUTF8String:&c[0]]];
  }
  }
  chunk += 1;
  }
  [line release];
  [batch release];
  return found;
 }

Now the main method, findInPortionMethod:needle:andAddTo:. This method tries to find a block surrounded by the stream\r\n and endstream\r\n markers in the data received so far. If the block has been found, it tries to inflate it and pass it to the findBatchNumberInChunk: method. After the block is processed, it gets removed from the buffer.

 + (bool)findInPortion:(NSMutableData *)portion needle:(NSString*)needle
  andAddTo:(NSMutableArray*)list {
  static char const* const streamStartMarker = "stream\x0d\x0a";
  static char const* const streamStopMarker = "endstream\x0d\x0a";
  bool found = false;
  while (true) {
  int const beginPosition = [self findInData:portion fromOffset:0
  needle:streamStartMarker];
  if (beginPosition == -1) break;
  int const endPosition = [self findInData:portion
  fromOffset:beginPosition needle:streamStopMarker];
  if (endPosition == -1) break;
  int const blockLength = endPosition + strlen(streamStopMarker) -
  beginPosition;
 
  char const* const zipped = [portion mutableBytes] + beginPosition +
  strlen(streamStartMarker);
  z_stream zstream;
  memset(&zstream, 0, sizeof(zstream));
  int const zippedLength = blockLength - strlen(streamStartMarker) -
  strlen(streamStopMarker);
 
  zstream.avail_in = zippedLength;
  zstream.avail_out = zstream.avail_in * 10;
  zstream.next_in = (Bytef*)zipped;
  char* const unzipped = malloc(zstream.avail_out);
  zstream.next_out = (Bytef*)unzipped;
  int const zstatus = inflateInit(&zstream);
  if (zstatus == Z_OK) {
  int const inflateStatus = inflate(&zstream, Z_FINISH);
  if (inflateStatus >= 0) {
  found = found || [BatchPDFParser
  findBatchNumberInChunk:unzipped needle:needle
  andAddTo:list];
  } else {
  NSLog(@"inflate() failed, error %d", inflateStatus);
  }
  } else {
  NSLog(@"Unable to initialize zlib, error %d", zstatus);
  }
  free(unzipped);
  inflateEnd(&zstream);
 
  int const cutLength = endPosition + strlen(streamStopMarker);
  [portion replaceBytesInRange:NSMakeRange(0, cutLength)
  withBytes:NULL length:0];
  }
  return found;
 }
 
 @end

DirectDownloadViewDelegate.h

This is a header file declaring callbacks from NSURLConnection:

 @protocol DirectDownloadViewDelegate<NSObject>
 
 - (void)setProgress: (float)progress;
 - (void)appendStatus: (NSString*)status;
 - (void)setCompleteDate: (NSString*)date;
 
 @end

DirectDownloadDelegate.h

The NSURLConnectionDelegate delegate.

 #import "DirectDownloadViewDelegate.h"
 
 @interface DirectDownloadDelegate : NSObject {
  NSError *error;
  BOOL done;
  BOOL found;
  NSMutableData *receivedData;
  float expectedBytes, receivedBytes;
  id<DirectDownloadViewDelegate> viewDelegate;
  NSString* needle;
 }
 
 - (id) initWithNeedle:(NSString*)aNeedle andViewDelegate:
  (id<DirectDownloadViewDelegate>)aViewDelegate;
 
 @property (atomic, readonly, getter=isDone) BOOL done;
 @property (atomic, readonly, getter=isFound) BOOL found;
 @property (atomic, readonly) NSError *error;
 
 @end

DirectDownloadDelegate.m

This file contains the NSURLConnectionDelegate implementation.

 #import <Foundation/Foundation.h>
 
 #import "DirectDownloadDelegate.h"
 #import "BatchPDFParser.h"
 
 @implementation DirectDownloadDelegate
 @synthesize error, done, found;

The initWithNeedle:andViewDelegate: constructor creates a delegate. Interestingly, the NSURLConnection delegate is parameterized by another delegate, DirectDownloadViewDelegate. The last one allows the downloader and PDF parser to publish the results to the screen.

 - (id) initWithNeedle:(NSString*)aNeedle andViewDelegate:
  (id<DirectDownloadViewDelegate>)aViewDelegate {
  viewDelegate = aViewDelegate;
  [viewDelegate retain];
 
  needle = [[NSString alloc] initWithString:aNeedle];
  receivedData = [[NSMutableData alloc] init];
  expectedBytes = receivedBytes = 0.0;
  found = NO;
 
  return self;
 }
 
 - (void) dealloc {
  [error release];
  [receivedData release];
  [needle release];
  [viewDelegate release];
  [super dealloc];
 }

The connectionDidFinishLoading: method is invoked when the connection is finished.

 - (void) connectionDidFinishLoading:(NSURLConnection *)connection {
  done = YES;
  NSLog(@"Connection finished");
  }

The connection:didFailWithError: method is invoked when an error occurs.

 - (void) connection:(NSURLConnection *)connection
  didFailWithError:(NSError *)anError {
  error = [anError retain];
  [self connectionDidFinishLoading:connection];
  }

The connection:didReceiveData: method is called when a portion of data is received from the socket. We try to extract the payload from this portion and print it out. Also we update the progress bar.

 - (void) connection:(NSURLConnection *)connection
  didReceiveData:(NSData *)someData {
  receivedBytes += [someData length];
  [viewDelegate setProgress:(receivedBytes / expectedBytes)];
  [receivedData appendData:someData];
 
  NSMutableArray* list = [[NSMutableArray alloc] init];
  bool foundInCurrentPortion = [BatchPDFParser
  findInPortion:receivedData needle:needle andAddTo:list];
  for (id batch in list) {
  NSLog(@"[%@]",
  [batch stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]);
  [viewDelegate appendStatus:batch];
  }
  [list release];
  found = found || foundInCurrentPortion;
  }

The last callback, connection:didReceiveReponse:, is called when HTTP headers are received. We extract the Content-Length header to update the progress bar correctly later on.

 - (void)connection:(NSURLConnection *)connection didReceiveResponse:
  (NSHTTPURLResponse *)someResponse {
  NSDictionary *headers = [someResponse allHeaderFields];
  NSLog(@"[didReceiveResponse] response headers: %@", headers);
  if (headers) {
  if ([headers objectForKey: @"Content-Length"]) {
  NSLog(@"Content-Length: %@", [headers objectForKey:
  @"Content-Length"]);
  expectedBytes = [[headers objectForKey:
  @"Content-Length"] floatValue];
  } else {
  NSLog(@"No Content-Length header found");
  }
  }
 }
 
 @end

NSURLConnectionDirectDownload.h

This file contains a declaration of an extra method we are adding to the NSURLConnection class. Note, there is something interesting going on here. We don’t need to have the sources of the NSURLConnection class. Objective-C has the concept of categories, allowing mixing in new methods to the existing class. I call our new category DirectDownload.

 @interface NSURLConnection (DirectDownload)
 
 + (BOOL) downloadAtURL:(NSURL *)url searching:(NSString*)batchNumber
  viewingOn:(id)viewDelegate;
 
 @end

NSURLConnectionDirectDownload.m

Now the final bit of the downloader. The function donwloadAtURL:searching:viewingOn: below creates a connection and kicks off downloading. Then it waits in a loop (NSRunLoop) until the file is received. This loop allows the rest of the application to process events and remain responsive. Interestingly, this function is abstracted from the particular UI. It uses the viewDelegate delegate to interact with the UI (update the progress bar and print out parsed pieces of the PDF file).

 #import <Foundation/Foundation.h>
 #import "DirectDownloadDelegate.h"
 
 @implementation NSURLConnection (DirectDownload)
 
 + (BOOL) downloadAtURL:(NSURL *)url searching:(NSString*)batchNumber
  viewingOn:(id)viewDelegate {
  NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
  initWithURL:url];
 
  DirectDownloadDelegate *delegate = [[[DirectDownloadDelegate alloc]
  initWithNeedle:batchNumber andViewDelegate:viewDelegate] autorelease];
  NSURLConnection *connection = [[NSURLConnection alloc]
  initWithRequest:request delegate:delegate];
  [request release];
 
  while ([delegate isDone] == NO) {
  [[NSRunLoop currentRunLoop] runUntilDate:[NSDate
  dateWithTimeIntervalSinceNow:1.0]];
  }
 
  if ([delegate isFound] != YES) {
  [viewDelegate appendStatus:@"This batch number is not found."];
  NSLog(@"This batch number is not found.");
  }
 
  NSLog(@"PDF is processed");
  [connection release];
 
  NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
  dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss";
  NSString* lastUpdateDate = [dateFormatter stringFromDate:[NSDate
  date]];
  NSLog(@"Last update at: %@", lastUpdateDate);
  [viewDelegate setCompleteDate:lastUpdateDate];
  [dateFormatter release];
 
  NSError *error = [delegate error];
  if (error != nil) {
  NSLog(@"Download error: %@", error);
  return NO;
  }
 
  return YES;
 }
 
 @end

ViewController.m

This file contains an implementation of the view controller. This file is not the same as in the full-blown UI application, because, obviously, we have no UI here. So, this significantly simplified version of the controller only has a few stub functions allowing checking whether the callbacks are actually invoked by the downloader.

 #import <Foundation/Foundation.h>
 
 #import "DirectDownloadViewDelegate.h"
 
 #define IBAction void

Then we declare an empty stub object mocking the view controller.

 @interface ViewController : NSObject <DirectDownloadViewDelegate>
 @end
 
 #import "NSURLConnectionDirectDownload.h"

There is a URL of our PDF file.

 static char const* const pdf =
  "http://photos.state.gov/libraries/unitedkingdom/164203/cons-visa/
  admin_processing_dates.pdf";

Now the view controller mock implementation.

 @implementation ViewController

The appendStatus: callback is invoked when we have discovered one more update related to the given batch number. The real view controller will append the information to a UI widget, but here we only log it.

 - (void) appendStatus:(NSString*)status {
  NSLog(@"appendStatus(): '%@'", [status
  stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]);
  // Some code is skipped here
  // because not required for the command line mode.

The setProgress: callback is called when the downloader wants to update the progress bar. Technically, we can print out the value that is passed in if we’re in doubt, but the values will be running from 0% to 100% with some step, so it pollutes the log with not-quite-useful stuff. As an exercise, readers could try logging the parameter via NSLog() and checking the result.

 - (void) setProgress:(float)progress {
  // Some code is skipped here
  // because not required for the command line mode.

The setCompleteDate: callback is called when the PDF file is fully downloaded. In this case our UI will update a label widget representing the last update time. But here, again, we simply log it.

 - (void) setCompleteDate:(NSString*)date {
  NSLog(@"setCompleteDate(): '%@'", date);
  // Some code is skipped here
  // because not required for the command line mode.

The last method in this file is updateBatchStatus:. This is a starting point that we use to kick off downloading. It accepts the batch number as a parameter and then calls NSURLConnection.

 - (bool) updateBatchStatus:(NSString*)batchNumber {
  NSURL *url = [[[NSURL alloc] initWithString:[NSString
  stringWithCString:pdf encoding:NSASCIIStringEncoding]] autorelease];
  return [NSURLConnection downloadAtURL:url searching:batchNumber
  viewingOn:self];
 }

main-cli.m

This is an entry point of our command-line application. We create a mock of the view controller, then after extracting the batch number from the command we kick off downloading.

 #import <Foundation/Foundation.h>
 #import "DirectDownloadDelegate.h"
 
 @interface ViewController : NSObject <DirectDownloadViewDelegate>
 - (bool) updateBatchStatus:(NSString*)batchNumber;
 @end
 
 int main(int argc, char *argv[]) {
  @autoreleasepool {
  ViewController* viewController = [ViewController alloc];
  [viewController updateBatchStatus:[NSString
  stringWithCString:argv[1] encoding:NSASCIIStringEncoding]];
  [viewController release];
  }
  return 0;
 }

Want to give it a whirl?

Okay, the Makefile. For Mac it’ll be very simple:

 files = \
  ViewController.m \
  BatchPDFParser.m \
  NSURLConnectionDirectDownload.m \
  DirectDownloadDelegate.m
  main-cli.m
 
 all: build run
 
 build:
  clang -o USVisaTest -DTESTING -framework Foundation -lz $(files)
 
 run:
  ./USVisaTest 20121456171

For GNUstep running on Windows it’ll be a bit clunky (this file needs to be named GNUmakefile, not Makefile):

 include $(GNUSTEP_MAKEFILES)/common.make
 
 TOOL_NAME = USVisa
 USVisa_OBJC_FILES = \
  ../ViewController.m \
  ../BatchPDFParser.m \
  ../NSURLConnectionDirectDownload.m \
  ../DirectDownloadDelegate.m \
  ../main-cli.m
 USVisa_TOOL_LIBS = -lz
 ADDITIONAL_OBJCFLAGS = -DTESTING
 CC = clang
 
 include $(GNUSTEP_MAKEFILES)/tool.make
 
 run:
  ./obj/USVisa 20121456171

Now we can build simply by running make. For example, on Windows, it should come up with something like:

 This is gnustep-make 2.6.2.
  Type 'mmake print-gnustep-make-help' for help.
 Making all for tool USVisa...
  Creating obj/USVisa.obj/../...
  Compiling file ViewController.m ...
  Compiling file BatchPDFParser.m ...
  Compiling file NSURLConnectionDirectDownload.m ...
  Compiling file DirectDownloadDelegate.m ...
  Compiling file main-cli.m ...
  Linking tool USVisa ...

And finally we can try running it using some test batch number:

 make run

In my case it printed the following:

 This is gnustep-make 2.6.2. Type 'mmake print-gnustep-make-help' for help.
  ./obj/USVisa 20121456171
  2012-06-19 17:27:11.472 USVisa[3420] [didReceiveResponse] response headers:
  {"Accept-Ranges" = bytes; "Cache-Control" = "max-age=600";
  Connection = "keep-alive"; "Content-Length" = 2237242;
  "Content-Type" = "application/pdf"; Date = "Tue, 19 Jun 2012 16:27:11 GMT";
  ETag = "\"4b2ca3e41de5ba4ae45670e776edfc3b:1339778351\"";
  "Last-Modified" = "Fri, 15 Jun 2012 16:06:15 GMT"; Server = Apache; }
  2012-06-19 17:27:11.604 USVisa[3420] Content-Length: 2237242
  2012-06-19 17:27:12.093 USVisa[3420] Found match: '20121456171'
  'send passport & new travel itinerary' '14-Jun-12'
  2012-06-19 17:27:12.104 USVisa[3420] [send passport &
  new travel itinerary\n14-Jun-12]
  2012-06-19 17:27:12.111 USVisa[3420] appendStatus():
  'send passport & new travel itinerary\n14-Jun-12'
  2012-06-19 17:27:13.769 USVisa[3420] Connection finished
  2012-06-19 17:27:13.774 USVisa[3420] PDF is processed
  2012-06-19 17:27:13.961 USVisa[3420] Last update at: 2012/06/19 16:27:13
  2012-06-19 17:27:13.972 USVisa[3420] setCompleteDate(): '2012/06/19 16:27:13'

So, we have proven that our PDF parser and downloader work as expected, and we can proceed to the proper iOS application. Unfortunately, Windows users cannot try any of the following code, but I hope they will enjoy watching it.

The Application UI Design

I wanted my application to be very simple: only one screen having just a text entry field for the batch number, a refresh button and the area to print out the status. For example:

usvisa-application-screenshot.jpg

Also to indicate the progress of downloading it may temporarily display a progress bar.

ViewController.h

This is a full implementation of the ViewContoller.h. I will be skipping details explained previously within the reduced, command-line version of the controller.

 #import <Foundation/Foundation.h>
 #import "DirectDownloadViewDelegate.h"
 
 #ifdef TESTING
 #define IBAction void
 @interface ViewController : NSObject <DirectDownloadViewDelegate>
 @end
 #else
 #import "ViewController.h"
 #endif
 
 #import "NSURLConnectionDirectDownload.h"
 
 static char const* const pdf = "http://photos.state.gov/libraries/
  unitedkingdom/164203/cons-visa/admin_processing_dates.pdf";
 
 @implementation ViewController
 
 #ifndef TESTING
 @synthesize updateProgressView, batchNumberTextField, statusTextView,
  lastUpdatedLabel, updateButton;
 #endif
 
 NSString* const PropertiesFilename = @"Properties";
 
 NSString *pathInDocumentDirectory(NSString *fileName) {
  NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains
  (NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *documentDirectory = [documentDirectories objectAtIndex:0];
  return [documentDirectory stringByAppendingPathComponent:fileName];
 }

appendStatus: is a callback from the PDF parse saying that the status update regarding our batch number has been found. We check that it is not empty and stick it to the screen.

 - (void) appendStatus:(NSString*)status {
  NSLog(@"appendStatus(): '%@'", [status
  stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]);
 #ifndef TESTING
  if ([[statusTextView text] length] == 0)
  [statusTextView setText:@"Status:\n"];
  [statusTextView setText:[[statusTextView text]
  stringByAppendingString:status]];
  [statusTextView setText:[[statusTextView text]
  stringByAppendingString:@"\n"]];
 #endif

setProcess: is another callback invoking when a chunk of data is received and we need to update the progress bar.

 - (void) setProgress:(float)progress {
 #ifndef TESTING
  updateProgressView.progress = progress;
 #endif
 }

setCompleteDate: is yet another callback updating the time of the last success update.

 - (void) setCompleteDate:(NSString*)date {
  NSLog(@"setCompleteDate(): '%@'", date);
 #ifndef TESTING
  [lastUpdatedLabel setText:date];
 #endif
 }

Finally, the updateBatchStatus: is a method we call to kick off downloading of the PDF.

 - (bool) updateBatchStatus:(NSString*)batchNumber {
  NSURL *url = [[[NSURL alloc] initWithString:[NSString
  stringWithCString:pdf encoding:NSASCIIStringEncoding]] autorelease];
  return [NSURLConnection downloadAtURL:url searching:batchNumber
  viewingOn:self];
 }

Now there are a few iOS-specific methods. viewDidLoad: is called by the application loader when the view is activated and ready to use. In this method we create a spinner indicator on the fly. We will be displaying it when downloading is in progress. Also we tweak heights of two buttons because for some reason the Xcode Interface Builder doesn’t allow changing it in the UI.

 #ifndef TESTING
 - (void)viewDidLoad
 {
  [super viewDidLoad];
  // Do any additional setup after loading the view,
  // typically from a nib.
  spinnerActivityIndicatorView = [[UIActivityIndicatorView alloc]
  initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  [spinnerActivityIndicatorView setColor:[UIColor blueColor]];
  CGSize size = [[self view] frame].size;
  [spinnerActivityIndicatorView setCenter:CGPointMake(size.width / 2,
  size.height / 2 + 60)];
  [self.view addSubview:spinnerActivityIndicatorView];
 
  CGRect rect = [self.updateButton bounds];
  rect.size.height += 10;
  [self.updateButton setBounds:rect];
 
  rect = [self.batchNumberTextField bounds];
  rect.size.height += 20;
  [self.batchNumberTextField setBounds:rect];
 
 #ifdef DEBUG
  NSLog(@"DEBUG mode");
 #endif
 }

viewDidUnload: is called when the view becomes inactive.

 - (void)viewDidUnload
 {
  [super viewDidUnload];
  // Release any retained subviews of the main view.
 }

shouldAutorotateToInterfaceOrientation: allows to control the screen rotation in the iOS application. Here we say that we only allow the portrait mode.

 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)
  interfaceOrientation
 {
  return (interfaceOrientation == UIInterfaceOrientationPortrait);
 }
 #endif

launchUpdate: is an action attached to an Update button on the screen. We disable the button itself, activate the progress bar and the spinner indicator, and invoke the updateBatchStatus: method. After it is finished we restore the things in the reverse order.

 - (IBAction)launchUpdate:(id)sender {
  [self setProgress:0.0];
 #ifndef TESTING
  [updateButton setEnabled: NO];
  [updateProgressView setHidden:NO];
 
  NSString* previousStatus = [statusTextView text];
  [statusTextView setText:@""];
 
  NSString* batchNumber = [batchNumberTextField text];
 
  [spinnerActivityIndicatorView startAnimating];
  BOOL const ok = [self updateBatchStatus:batchNumber];
  [spinnerActivityIndicatorView stopAnimating];
 
  if (!ok) {
  UIAlertView *alert =
  [[UIAlertView alloc] initWithTitle:@"Error"
  message:@"Internet connectivity
  problem"
  delegate:self cancelButtonTitle:nil
  otherButtonTitles:@"OK", nil];
  [alert show];
  [alert release];
  [statusTextView setText:previousStatus];
  }
 
  [updateProgressView setHidden:YES];
  [updateButton setEnabled: YES];
 #endif
 }

The saveProperties: method stores the content of the screen element to a file.

 - (void) saveProperties {
  NSDictionary *props = [[NSDictionary alloc] initWithObjectsAndKeys:
 #ifndef TESTING
  batchNumberTextField.text,
  @"batchNumberTextField",
  statusTextView.text, @"statusTextView",
  lastUpdatedLabel.text, @"lastUpdatedLabel",
 #endif
  nil];
  for (NSString* key in props) {
  NSLog(@"%@ - %@", key, [props objectForKey:key]);
  }
 
  NSString* filename = pathInDocumentDirectory(PropertiesFilename);
  if ([props writeToFile:filename atomically:YES] == NO)
  NSLog(@"Unable to save properties into file [%@]", filename);
 
  [props release];
 }

The loadProperties: method is a counterpart of saveProperties:.

 - (void) loadProperties {
  NSDictionary *props = [[NSDictionary alloc]
  initWithContentsOfFile:pathInDocumentDirectory(PropertiesFilename)];
  for (NSString* key in props) {
  NSLog(@"%@ - %@", key, [props objectForKey:key]);
  }
 
 #ifndef TESTING
  [batchNumberTextField setText:[props
  objectForKey:@"batchNumberTextField"]];
  [statusTextView setText:[props objectForKey:@"statusTextView"]];
  [lastUpdatedLabel setText:[props objectForKey:@"lastUpdatedLabel"]];
 #endif
  [props release];
 }
 
 - (IBAction)textFieldReturn:(id)sender {
 #ifndef TESTING
  [sender resignFirstResponder];
 #endif
 }
 
 -(IBAction)backgroundTouched:(id)sender {
 #ifndef TESTING
  [batchNumberTextField resignFirstResponder];
 #endif
 }
 
 @end

This is basically it! We have all the parts to build the application.

I’ve uploaded the project to GitHub—usvisa, so please free to mess around. Any feedback is more than appreciated.

And One More Thing!

The catchy icon is the most important thing if you’re out to sell zillions of copies of your app. In our example, we’ll simply take it from the most respectful source—Wikipedia. It will be The Great Seal of the United States.

Great_Seal_of_the_US.jpg

P.S.

Funnily enough I decided to open-source the application and dissect it in this article after it had been rejected by Apple from the AppStore. They referred to a statement in the official guidelines for iOS developers saying that applications with very limited functionality, and which can be re-implemented in HTML5, will be rejected. They want to avoid more farting or displaying single static pictures applications. I would probably argue with the censor about “limited functionality” or “can be implemented as an HTML5 app,” but I decided not do so. First, I appreciate Apple’s effort to reduce the amount of crap in the AppStore, and second, I had to dive quite deep into Objective-C and Cocoa, and I don’t regret it. I’m currently working on two more apps.

Alexander Demin is a software engineer and Ph.D. in Computer Science. Constantly exploring new technologies, he believes that something amazing is always out there. He can be contacted at alexander@demin.ws or through his homepage and blog.

Send the author your feedback or discuss the article in the magazine forum.