tutorial_-code-sharing-via-static-libraries-and-cross-project-references

I’m happy to announce that my “Xcode cross-project code sharing” tutorial has received over 1,000 hits and was recently “syndicated” by the excellent iPhone developer journal Mobile Orchard (they told me the tutorial received about 20,000 hits on their site–wow)! Mobile Orchard really is an excellent resource, especially in regards to their podcasts–I recommend it to any other iPhone developers.

Finding an elegant way to reuse and share code (i.e., libraries) across separate iPhone applications can be a bit tricky at first, especially considering Apple’s restrictions on dynamic library linking and custom Frameworks. Most people agree that the best solution is to use static libraries. This tutorial builds on that solution, showing how your Xcode project can reference a second Xcode project–one which is used to build a static library. This allows you to automatically build that static library with the rest of your app, using your current build configuration (e.g., debug, release, etc.) and avoid pre-building several versions of the library separately (where each version was built for a specific environment/configuration). [click to continue…]

I’ve been using a tool called Doxygen to document my Objective-C code; it’s comparable to javadoc in the Java world, but can be used with a much wider variety of languages (PHP, C#, and Java, to name a few). In a nutshell, you use a few special symbols when writing normal code comments and Doxygen can then generate HTML, class diagrams, etc., from your source code. (You can see a previous post of mine with some sample code for an example.)

One nice feature of Doxygen is that it can handle C preprocessor directives just like an actual build tool (e.g., gcc), and even generate documentation for them. For example, you might code your own “logging” macro that maps to the standard NSLog() function as follows:

/**
  Custom logging macro blah blah blah...
 */
#define MYLOG(...) NSLog(__VA_ARGS__)

Doxygen will actually include define statements like this in the HTML documentation it generates, including your code comments, as long as you have  ENABLE_PREPROCESSING=YES in your doxygen.config file.

While this is a great feature, you can run into trouble if you forget that conditional statements like #if will also be processed. For example, let’s say we only wanted to include some code if we’re building for the iPhone. Although it’s a bit contrived, we might rewrite the above macro as follows:

#if TARGET_OS_IPHONE

/**
  Custom logging macro blah blah blah...
 */
#define MYLOG(...) NSLog(__VA_ARGS__)

#endif

TARGET_OS_IPHONE is a standard variable that is automatically defined when you do regular builds in Xcode. But when you run Doxygen you’re not doing a “regular build” via Xcode and TARGET_OS_IPHONE isn’t defined. Assuming Doxygen’s preprocessor is enabled, the above #if statement would be false and the contained code (i.e., the MYLOG() definition) wouldn’t be included in your HTML documentation.

The trick, as I found recently, is to use the PREDEFINED setting in the doxygen.config file. This lets you define preprossor variables to mimic the Xcode build environment. Continuing the example, you would put the following in doxygen.config:

PREDEFINED = TARGET_OS_IPHONE=1

The result is that the TARGET_OS_IPHONE variable will not only be defined, but set to “1″, and MYLOG() will be processed by Doxygen.

Updated 8/18/09: Added info for 3GS (Thanks Jamey).

Erica Sadun recently shared a great post on Ars Technica showing how to determine specifically which model of iPhone or iPod Touch is being used, as opposed to the high-level “iPhone vs. iPod Touch” information made available via the UIDevice -model: method. As she points out, knowing the exact model can be pretty helpful as there are some significant differences between the different devices (e.g., the second-generation iPod Touch has speakers, while the first-generation model does not).

In a nutshell, you use a C function called sysctlbyname to get the hardware model (and if you’re not familiar with doing stuff in C like manually allocating and freeing memory, the code sample is really helpful). Here’s a snippet from the original post:

- (NSString *) platform
{
  size_t size;
  sysctlbyname("hw.machine", NULL, &size, NULL, 0);
  char *machine = malloc(size);
  sysctlbyname("hw.machine", machine, &size, NULL, 0);
  /*
  Possible values:
  "iPhone1,1" = iPhone 1G
  "iPhone1,2" = iPhone 3G
  "iPhone2,1" = iPhone 3GS
  "iPod1,1"   = iPod touch 1G
  "iPod2,1"   = iPod touch 2G
  */
  NSString *platform = [NSString stringWithCString:machine];

  free(machine);
  return platform;
}

I recently had to write a utility for compressing data in memory using the gzip format. Thankfully, there’s a C library called zlib you can use to do the actual compression (and thankfully you can link to libz.dylib on the iPhone). Using the library is not trivial, however, so I had to spend a day reading the zlib headers/documentation and also searching for examples from other developers (one example shared by Robbie Hanson was particularly helpful).

While Robbie’s example is great, I wanted something a bit more robust and easier to “plug in” to any existing project. As part of making it “plug ‘n play,” I also wanted to make it developer-friendly: if something goes wrong, the utility should be helpful in solving the problem instead of just exiting with a cryptic error code. That means adding a healthy amount of documentation and descriptive error message logging so that Joe Developer–who just wanted to copy and paste the utility into his project and move on–can quickly understand the code and the error message if problems come up.

Here’s an example of how you would use the class:

/**
 @file LFCGzipUtility.h
 @author Clint Harris (www.clintharris.net)

 Note: The code in this file has been commented so as to be compatible with
 Doxygen, a tool for automatically generating HTML-based documentation from
 source code. See http://www.doxygen.org for more info.
 */

#import <Foundation/Foundation.h>
#import "zlib.h"

@interface LFCGzipUtility : NSObject
{

}

/***************************************************************************//**
 Uses zlib to compress the given data. Note that gzip headers will be added so
 that the data can be easily decompressed using a tool like WinZip, gunzip, etc.

 Note: Special thanks to Robbie Hanson of Deusty Designs for sharing sample code
 showing how deflateInit2() can be used to make zlib generate a compressed file
 with gzip headers:
 http://deusty.blogspot.com/2007/07/gzip-compressiondecompression.html

 @param pUncompressedData memory buffer of bytes to compress
 @return Compressed data as an NSData object
 */
+(NSData*) gzipData: (NSData*)pUncompressedData;

@end
/**
 @file LFCGzipUtility.m
 @author Clint Harris (www.clintharris.net)

 Note: The code in this file has been commented so as to be compatible with
 Doxygen, a tool for automatically generating HTML-based documentation from
 source code. See http://www.doxygen.org for more info.
 */

#import "LFCGzipUtility.h"

@implementation LFCGzipUtility

/*******************************************************************************
 See header for documentation.
 */
+(NSData*) gzipData: (NSData*)pUncompressedData
{
	/*
	 Special thanks to Robbie Hanson of Deusty Designs for sharing sample code
	 showing how deflateInit2() can be used to make zlib generate a compressed
	 file with gzip headers:
	 http://deusty.blogspot.com/2007/07/gzip-compressiondecompression.html
	 */

	if (!pUncompressedData || [pUncompressedData length] == 0)
	{
		NSLog(@"%s: Error: Can't compress an empty or null NSData object.", __func__);
		return nil;
	}

	/* Before we can begin compressing (aka "deflating") data using the zlib
	 functions, we must initialize zlib. Normally this is done by calling the
	 deflateInit() function; in this case, however, we'll use deflateInit2() so
	 that the compressed data will have gzip headers. This will make it easy to
	 decompress the data later using a tool like gunzip, WinZip, etc.

	 deflateInit2() accepts many parameters, the first of which is a C struct of
	 type "z_stream" defined in zlib.h. The properties of this struct are used to
	 control how the compression algorithms work. z_stream is also used to
	 maintain pointers to the "input" and "output" byte buffers (next_in/out) as
	 well as information about how many bytes have been processed, how many are
	 left to process, etc. */
	z_stream zlibStreamStruct;
	zlibStreamStruct.zalloc    = Z_NULL; // Set zalloc, zfree, and opaque to Z_NULL so
	zlibStreamStruct.zfree     = Z_NULL; // that when we call deflateInit2 they will be
	zlibStreamStruct.opaque    = Z_NULL; // updated to use default allocation functions.
	zlibStreamStruct.total_out = 0; // Total number of output bytes produced so far
	zlibStreamStruct.next_in   = (Bytef*)[pUncompressedData bytes]; // Pointer to input bytes
	zlibStreamStruct.avail_in  = [pUncompressedData length]; // Number of input bytes left to process

	/* Initialize the zlib deflation (i.e. compression) internals with deflateInit2().
	 The parameters are as follows:

	 z_streamp strm - Pointer to a zstream struct
	 int level      - Compression level. Must be Z_DEFAULT_COMPRESSION, or between
	                  0 and 9: 1 gives best speed, 9 gives best compression, 0 gives
	                  no compression.
	 int method     - Compression method. Only method supported is "Z_DEFLATED".
	 int windowBits - Base two logarithm of the maximum window size (the size of
	                  the history buffer). It should be in the range 8..15. Add
	                  16 to windowBits to write a simple gzip header and trailer
	                  around the compressed data instead of a zlib wrapper. The
	                  gzip header will have no file name, no extra data, no comment,
	                  no modification time (set to zero), no header crc, and the
	                  operating system will be set to 255 (unknown).
	 int memLevel   - Amount of memory allocated for internal compression state.
	                  1 uses minimum memory but is slow and reduces compression
	                  ratio; 9 uses maximum memory for optimal speed. Default value
	                  is 8.
	 int strategy   - Used to tune the compression algorithm. Use the value
	                  Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data
	                  produced by a filter (or predictor), or Z_HUFFMAN_ONLY to
	                  force Huffman encoding only (no string match) */
    int initError = deflateInit2(&zlibStreamStruct, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
	if (initError != Z_OK)
	{
		NSString *errorMsg = nil;
		switch (initError)
		{
			case Z_STREAM_ERROR:
				errorMsg = @"Invalid parameter passed in to function.";
				break;
			case Z_MEM_ERROR:
				errorMsg = @"Insufficient memory.";
				break;
			case Z_VERSION_ERROR:
				errorMsg = @"The version of zlib.h and the version of the library linked do not match.";
				break;
			default:
				errorMsg = @"Unknown error code.";
				break;
		}
		NSLog(@"%s: deflateInit2() Error: \"%@\" Message: \"%s\"", __func__, errorMsg, zlibStreamStruct.msg);
		[errorMsg release];
		return nil;
	}

	// Create output memory buffer for compressed data. The zlib documentation states that
	// destination buffer size must be at least 0.1% larger than avail_in plus 12 bytes.
	NSMutableData *compressedData = [NSMutableData dataWithLength:[pUncompressedData length] * 1.01 + 12];

	int deflateStatus;
	do
	{
		// Store location where next byte should be put in next_out
		zlibStreamStruct.next_out = [compressedData mutableBytes] + zlibStreamStruct.total_out;

		// Calculate the amount of remaining free space in the output buffer
		// by subtracting the number of bytes that have been written so far
		// from the buffer's total capacity
		zlibStreamStruct.avail_out = [compressedData length] - zlibStreamStruct.total_out;

		/* deflate() compresses as much data as possible, and stops/returns when
		 the input buffer becomes empty or the output buffer becomes full. If
		 deflate() returns Z_OK, it means that there are more bytes left to
		 compress in the input buffer but the output buffer is full; the output
		 buffer should be expanded and deflate should be called again (i.e., the
		 loop should continue to rune). If deflate() returns Z_STREAM_END, the
		 end of the input stream was reached (i.e.g, all of the data has been
		 compressed) and the loop should stop. */
		deflateStatus = deflate(&zlibStreamStruct, Z_FINISH);

	} while ( deflateStatus == Z_OK );		

	// Check for zlib error and convert code to usable error message if appropriate
	if (deflateStatus != Z_STREAM_END)
	{
		NSString *errorMsg = nil;
		switch (deflateStatus)
		{
			case Z_ERRNO:
				errorMsg = @"Error occured while reading file.";
				break;
			case Z_STREAM_ERROR:
				errorMsg = @"The stream state was inconsistent (e.g., next_in or next_out was NULL).";
				break;
			case Z_DATA_ERROR:
				errorMsg = @"The deflate data was invalid or incomplete.";
				break;
			case Z_MEM_ERROR:
				errorMsg = @"Memory could not be allocated for processing.";
				break;
			case Z_BUF_ERROR:
				errorMsg = @"Ran out of output buffer for writing compressed bytes.";
				break;
			case Z_VERSION_ERROR:
				errorMsg = @"The version of zlib.h and the version of the library linked do not match.";
				break;
			default:
				errorMsg = @"Unknown error code.";
				break;
		}
		NSLog(@"%s: zlib error while attempting compression: \"%@\" Message: \"%s\"", __func__, errorMsg, zlibStreamStruct.msg);
		[errorMsg release];

		// Free data structures that were dynamically created for the stream.
		deflateEnd(&zlibStreamStruct);

		return nil;
	}
	// Free data structures that were dynamically created for the stream.
	deflateEnd(&zlibStreamStruct);
	[compressedData setLength: zlibStreamStruct.total_out];
	NSLog(@"%s: Compressed file from %d KB to %d KB", __func__, [pUncompressedData length]/1024, [compressedData length]/1024);

	return compressedData;
}

@end