Tag Archives: Xcode

Managing Assets & Viewing Bundle Contents

Managing all of the assets that go into an iPhone app via Xcode can sometimes be tedious.  It seems somewhat counter intuitive to me to have the IDE manage these resources too.  Maybe I’m in the minority, but I’d prefer to just work with source code and NIBs in Xcode and keep track of the “other stuff” on my own.

The other day I was looking around for a solution to remove the non-Interface Builder assets from Xcode, organize these files into folders within my bundle, and remove the tons of Groups or Folder References from the IDE.  Happily, the answer wasn’t too bad.

Xcode Options

When you drag a resource into Xcode, it presents you with a dialog asking how to treat it:

asset_dialog

Starting from the top, the checkbox will copy the assets (and preserve the folder hierarchy, if present) while placing the resources at the root folder of the destination group.  The default radio box is to “Recursively create groups for any added folders” which is generally what you’ll want, especially if you’re using Interface Builder too.  In your bundle, these resources are placed at the “root” meaning to access them in your code, you’ll just need to specify a name.

The other radio option, “Create Folder References…”, creates a folder tree both inside Xcode and in your bundle.  However, the resources cannot be seen by Interface Builder and you’ll need to use a path if you load them at run-time.

Build Phase Copy

If you like the folder hierarchy in your bundle, or just want to move this management largely out of Xcode, it’s pretty easy to do it on your own by adding a new build phase, as shown below.  Right click on your Target and follow the cascading menus.

build_phase

In the build phase, add the following script

PBXCP=${DEVELOPER_DIR}/Library/PrivateFrameworks/DevToolsCore.framework/Resources/pbxcp
BASE=${PROJECT_DIR}/assets
DST=${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}
${PBXCP} -exclude .svn ${BASE} "${DST}"

The script calls an iPhone tool to recreate your folder structure inside your build directory before the final bundle is created.  With the script, I can remove any links or asset associations within Xcode except those I want to use in IB.  I maintain a folder “assets” off my product directory where I place any files that I’d like bundled in my app.

Checking Bundle Contents

Perhaps a trick most folks know, but I thought it was worth mentioning, is you can inspect and check the contents of your app bundle by following either navigating there via the Terminal or right clicking the app and selecting “Show Package Contents”.

I had a question about the database format of iGive Blood that shipped in version 1.0 and managed to find an old build from Apple.  For products that you’ve shipped, it’s almost the same.

  1. In iTunes, right click the app and select “Show in Finder”
  2. Copy the IPA file to your desktop
  3. Rename the extension to ZIP
  4. Double click the ZIP to create an uncompressed folder.
  5. Open the Payload folder which has the app.

It’s actually a bit disappointing that it’s so easy to get to the package contents especially as the contents aren’t obscured.  Maybe that’s a subject for another post…

Security Updates, Xcode & Subversion

In addition to my full time job as a developer, I get occasional opportunities to moonlight as a system administrator.  Recently, we updated our Macs to the latest security patch Apple pushed out – and lo, our subversion integration with Xcode broke.  Sigh.

Back when we were initially setting up boxes for development, I very dimly recall there was some “shell magic” via the Terminal that needed to happen to get all the parts working together.  I think this was largely because Xcode preferred to work with an earlier version of subversion, perhaps 1.4.x – and it was no longer readily available at the time.

None the less, it’s easy to forget all the configuration steps in setting up a developer box – especially stuff that takes only a couple hours to figure out a nearly a year ago.  We’re not yet to Snow Leopard, so our tools may be a little dated: Xcode is 3.1.4 and Subversion is 1.5.5.

Symptoms

After the security upgrade, we fired up Xcode and noticed that there were:

  • No modified files in the project.
  • Right clicking a source file showed only “Get Annotations” in the Groups & Files pane.
  • Viewing the SCM | Repositories dialog (from the main menu) gave an error: 180001 unable to connect to the repository.
  • Even more scary was the Terminal message: svn: Expected FS format ‘2’; found format ‘3’

Solutions

Unfortunately, determining the solution wasn’t particularly clear.  Doing some Google searches on the error messages produced limited results.  The initial reaction was that something went wrong with the repository, but it was just working!  Running, “svnadmin verify” against the repository produced the File System format error message which was ominous.

The first thought was that perhaps the security patch affected the PATH variable.  Mac OS X ships with an older version of the subversion binaries and maybe we were picking up the wrong one.  On the Mac, various shell variables including your path are set in the .profile file found in your home directory.  A quick in the Terminal showed that /usr/bin was first in the path and /usr/local/bin (home to our newer svn) was last.  No problem – a quick edit and all was better.  Well… not quite.

Adjusting the path restored subversion from the Terminal and even the svnX app.  But, Xcode still stoutly refused to acknowledge the existence of the repository.  Grrr!  But, a step in the right direction and at least our repository was okay.

The breakthrough was trying to “Get Annotations” on a file.  There was a much more descriptive error – “155021 (Unsupported working copy format) please get a newer Subversion client.”  This jogged something loose in my mind and I dimly recall having to change some of the system libraries to get Xcode working with the newer Subversion.

After another quick search I found two other folks reported the same thing (Paul Solt and Blind Genius).  I’ll restate the solution here, but effectively Xcode is dynamically linking with the older Subversion libraries found in /usr/lib.  To work with newer builds of Subversion, you’ll need to create symbolic links to the newer dynamic libraries as follows:

cd /usr/lib

sudo mkdir svn.orig

sudo mv libap*-1.dylib svn.orig

sudo mv libsvn*-1.dylib svn.orig

sudo mv libap*-1.0.dylib svn.orig

sudo mv libsvn*-1.0.dylib svn.orig

sudo ln –s /opt/subversion/lib/*-1.0.dylib .

sudo ln –s /opt/subversion/lib*-1.dylib .

After adding the symbolic links, restart Xcode and things should be back to normal.

Cautions

One thing worth reverting is the PATH change.  Modifying the search order for executables might be considered a security risk as often 3rd party binaries (particularly shell ones) traditionally reside in /usr/local/bin and you might end up running something unexpected.  Perhaps safer is to create symbolic links to the binaries in /usr/bin, or alternatively command aliases in your .profile to run the correct version.

Adding Build Versions via Xcode

One of the things I was keen on setting up early in development was the automatic injection of a build number for our products.  Having a build number is vital when handing out early “pre release” versions of the app to testers for review.  During development, there are likely to be a number of builds and release candidates – and you’ll need a way to differentiate between them.

It turns out I wasn’t alone as a quick search on Google provided a wealth of possible options.

AGVTOOL

Apple provides a generic versioning build tool.  It provides a number of useful functions, like hooking directly into CVS or Subversion to commit modified code.  In addition, it automatically creates a CFBundleVersion key in your info.plist file.

By default, CFBundleVersion is 1.0 in the info.plist file.  But, there’s another key, CFBundleShortVersionString which can be used for this same purpose.  Thus, you could set the short version to “1.0” and let the agvtool set the CFBundleVersion key to the current build number.

If you automate your builds on a neutral machine, this technique is definitely something to consider.  It seems the most natural fit for generating a build number that can be referenced in an app.  There are a number of other folks who have tutorials and walkthroughs for exactly how to set this tool up for use in development:

Inner Exception

Chris Hanson’s Blog

Jamie Montgomerie’s Blog

SCRIPTING

While the agvtool has some definite benefits, it felt a bit “manual” to me.  Sure, you can hook it up as a post-build script or on a separate box that does nightly builds (or whatever) but I was looking for something else – like the revision number instead of a build number, per say, that was always updated without a ton of extra work.

Using the revision number from Subversion seemed an easier way to link the code state with the build number without the extra hassle of tagging or branching specifically for the purpose of sharing a build with testers or external stakeholders.  So, I turned my attention to script based solutions.

Luckily, there are a number of posts that describe how to do the exact same thing with a variety of scripting tools and languages.  Most hinge on creating a custom post-build step, then running either some Perl, Ruby, or shell script to substitute in the desired number into a plist file associated with your project.

Another choice in scripting tools is “PlistBuddy” which can read/write values to plists – but you’d need to wrap it in a shell script and at that point it’s debatable whether using Perl (or the shell) to perform the insertion isn’t just as easy.  PlistBuddy doesn’t solve the potential problem of parsing the revision number from Subversion either.

Adding something like this into your Xcode is trivial.  Under “Groups & Files” expand the Targets section and right-click the target to select Add –> New Build Phase –> New Run Script Phase.  A window will pop up where you can insert your own commands into the overall build.  Here’s some sample code from other folks:

Ruby – Jeff LaMarche, author of “Beginning iPhone Development.”

Git – Marcus Zarra.

Perl – Stackoverflow.

In the end, I took the route proposed by Daniel Jalkut on Red Sweater using Subversion with a couple minor modifications:

# Xcode auto-versioning script for Subversion
# by Axel Andersson, modified by Daniel Jalkut to add
# “–revision HEAD” to the svn info line, which allows
# the latest revision to always be used.

use strict;

die “$0: Must be run from Xcode” unless $ENV{“BUILT_PRODUCTS_DIR”};

# Get the current subversion revision number and use it to set the CFBundleVersion value
my $REV = `/usr/local/bin/svnversion -n ./`;
my $INFO = “$ENV{BUILT_PRODUCTS_DIR}/$ENV{WRAPPER_NAME}/version.plist”;

my $version = $REV;

# (Match the last group of digits and optional letter M/S):
($version =~ m/\d+[MS]*$/) && ($version = $&);

die “$0: No Subversion revision found” unless $version;

open(FH, “$INFO”) or die “$0: $INFO: $!”;
my $info = join(“”, <FH>);
close(FH);

$info =~ s/r0000/r$version/;
print $version;
print $INFO;

open(FH, “>$INFO”) or die “$0: $INFO: $!”;
print FH $info;
close(FH);

The major change from the original is that I write to a separate plist file (version) instead of Info.plist with a default key of “r0000” which is used for the substitution.  Really there’s not much difference and the choice of which scripting language is largely a personal choice for maintenance sake.

Automating SQLite Database Creation

“If you want more effective programmers, you will discover that they should not waste their time debugging, they should not introduce the bugs to start with.”
Edsger Dijkstra

During the software development process, the more non-deterministic processes that enter into your workflow the greater chance of introducing unintended bugs or functional regression.  There’s nothing more frustrating than to fix a bug (or make an improvement) only to have something else in your app break.  Thus, it’s generally considered good practice to automate as much of your pipeline (from build to asset creation) as possible.  If you can automate something, do so!  It’ll save you time and effort later.

If you decide to use SQLite in your iPhone app, there are a few steps to follow in order to automate re-creation of your database.

For Daily Value, I used a couple of tools to construct and later manipulate the database.  On the PC, the process started with Microsoft Access to transform the relational database from the USDA into a number of Excel spreadsheets – one for each table.  From Access, this was accomplished by right clicking the tables and selecting “Export | Excel”.

Microsoft Excel 2007 was used heavily to manipulate and process the input data before it was ready to export into SQLite.  There are some subtle and sometimes frustrating differences between Excel 2007 and 2003.  It felt to me like exporting was more flexible in the earlier version.  Basically, the “standard” CSV is “comma separated values” which created an input parsing problem for both SQLite Manager for Firefox and the command line tool, sqlite3, because the commas used for field delimiters.

I dimly thought that in older versions of Excel, when exporting to CSV, a series of dialogs took you through the process and allowed you to specify a custom delimiter.  In a file full of strings with commas already, the ability to set the token was great.  In my case, I wanted to export the data using the pipe (|) symbol.  It took some digging but exporting to CSV in 2007 to a flavor that SQLite liked required changing the output separator token via the Control Panel!

Here’re the basic steps:

  1. With Excel closed, open the Control Panel
  2. Double click the Regional and Language Options
  3. On the Regional Options tab, click on the Customize button
  4. On the Numbers tab, change the List separator to “|” (without the quotes)

Now you can “Save As” in Excel using CSV with the custom separator.

I used SQLite Manager early in the development process because it was very convenient to not only import data, but it was nice working in a visual environment as the tables continued to undergo design changes.  Even after I automated the database creation, the tool was useful to inspect run-time changes from the Simulator.  It was also nice to see the SQL the tool generated when creating tables, indices, or other operations on the data.

Once the table structure solidified, I wrote a simple “text input file” to re-create the database using the sqlite3 schema command.

  1. Gather up the structure of your tables by using either SQLite Manager or sqlite3.  In the Firefox plug-in, SQLite Manager has a tab that describes the table structure.  Or, launch the Terminal, and open your database with sqlite3, then type “.schema <table_name>” to get the structure.
  2. Copy this information to TextEdit (or an editor of your preference).  The top portion of this file should issue all the table creation commands required to setup your database structure.
  3. Then add the sqlite3 import commands to import the CSV files.

.mode csv

.separator |

.import table_1.csv table_1

And so on.  If you need to insert data into the tables, or create indices on any tables, you can insert these commands at the end of the file.  My input file, named schema.txt, looked something like this:

CREATE TABLE “food” (“pk” INTEGER PRIMARY KEY NOT NULL, “group” INTEGER, “name” TEXT, “desc” TEXT);

CREATE TABLE “version” (“ver” INTEGER);

.mode csv

.separator |

.import food.csv food

CREATE INDEX idx_food_pk ON food (“pk” ASC);

INSERT INTO version (ver) VALUES (“2”);

A couple notes on the file format.  You can span SQL commands on multiple lines in order to make the file more human readable.  The semi-colon is important for any SQL statements used in the file.  You should remove any column header information from the CSV file prior to using the “.import” command as the column types most likely won’t match and cause the import to fail.  Also, the CSV files reside in the same directory as where you’ll create the database and issue the sqlite3 command as shown below:

%sqlite3 database.sqlite < schema.txt

If you really wanted to get more sophisticated, you could hook running (and copying) this freshly created database into Xcode as a build step.  In another entry, I’ll talk about how I use something similar to auto version builds based on the repository revision.

Data persistence choices

In roughing out the early data framework for Daily Value, one of the early considerations was data persistence – particularly user created data.  The app’s requirements included supporting user entered food data that could be treated “on par” with shipped food data.

Looking around at what the iPhone SDK supported, a couple choices quickly presented themselves: archiving, property lists, and SQLite.  While the temptation was to leap right into a relational database solution, some consideration was made for the other choices and in truth, Daily Value employs all of these techniques for various pieces of data.

There’s a nice write up in “Beginning iPhone Development” by Dave Mark and Jeff LaMarche so I won’t dive too deeply into it except to say property lists and archiving were discarded.  Although writing data in a binary format with archiving had some appeal for data obfuscation purposes, it would have also meant writing a bunch of “database-like” functionality that didn’t seem a good technical investment in time or resources.  So, SQLite was what I went with.

SQLite offers a lot of upside:

  • The API is written in C and is easy to navigate.
  • Storage and manipulation of the data was offloaded to the database framework.
  • There are a number of free tools that can be used to browse and quickly inspect the data (ex. SQLite Manager for FireFox)
  • Creation can be automated
  • Good performance on complex queries

The biggest drawback is an apparent lack of authentication or way to protect the data placed into the database.  Since our data was publicly available, this wasn’t much of an issue but it might be for some other apps.

The initial design called for all the data – both shipped and user generated – to be stored in a single database file.  The thought being that it’d be easier to code queries against and manage.  User data would contain some differentiator (either a column identifier or field) to denote it as “editable” (you don’t want folks mucking about with your data).

Apps that use persistent data, or write back cannot do so to files stored within their application bundle or else it breaks the code signing and things stop working.  So, when the app initially loads, the source data is copied to the Documents directory and opened from there.  Of course, the app checks first to see if the file already exists so it doesn’t overwrite an existing file.

Test rigs built for the simulator seemed to indicate that this was viable, but putting the app on the actual device proved otherwise.  Apple requires that apps fully launch quickly after no more than 5 seconds.  The first database prototypes were nearly 4 MB.  The bulk initial copy caused startup to be unacceptable taking over 10 seconds!

Trimming the database helped.  With a little bit of effort in making tables more relational and dropping columns that weren’t strictly required, the size got squeezed down to just under 2 MB.  Even so, the copy took too long – so what to do?

After digging about through the SQLite documentation, I came across the SQL “attach” command.  One neat feature in SQLite is that it supports the notion of opening multiple databases and treating them (generally) as a single entity.  Care was taken to create a “template” user database with pre-created tables that were different than the app supplied database.  While not strictly necessary since SQLite will allow you to have duplicate table names, it seemed prudent and an easy “design time” step.

When the app launches, if it doesn’t already exist, the “empty” user database (all 12 kb of it) is copied from the main bundle to the Documents directory and opened.  Then, the app “attaches” to the large bundle database with the restriction that none of the bundled data can be modified.  Startup times were acceptable and the additional complexity was very minimal due to the SQLite abstraction support.

Here’s a bit of code to get the job done – assuming that dbHandle is a member variable of your data class.

- (void)copyDataIfNeeded
{
   NSFileManager *fm = [NSFileManager defaultManager];
   NSArray *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   NSString *docDir = [docPath objectAtIndex:0];
   NSString *writePath = [docDir stringByAppendingPathComponent:@”user.db”];

   BOOL success = [fm fileExistsAtPath:writePath];
   if (success)
      return;

   NSError *error;
   NSString *dbPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:”user.db”];
   success = [fm copyItemAtPath:dbPath toPath:writePath error:&error];
   if (!success)
      NSAssert1(0, @”failed to create writable database with msg ‘%@’”, [error localizedDescription]);
}

- (void)attachDatabase
{
   [self copyDataIfNeeded];

   NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   NSString *docDir = [path objectAtIndex:0];
   NSString *openPath = [docDir stringByAppendingPathComponent:”user.db”];

   if (sqlite3_open([openPath UTF8String], &dbHandle) == SQLITE_OK)
   {
      NSString *sysDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:”main.db”];
      NSString *sqlString = [NSString stringWithFormat:@”ATTACH DATABASE ‘%@’ AS main”, sysDBPath];

      const char *sql = [sqlString UTF8String];
      sqlite3_stmt *statement;
      int success = 0;

      success = sqlite3_prepare_v2(dbHandle, sql, -1, &statement, NULL);
      NSAssert1(success == SQLITE_OK, @”Failed to prepare ATTACH with msg ‘%s’”, sqlite3_errmsg(dbHandle));

      success = sqlite3_step(statement);
      NSAssert1(success == SQLITE_DONE, @”Failed to execute ATTACH with msg ‘%s’”, sqlite3_errmsg(dbHandle));

      sqlite3_finalize(statement);
   }
   else
       NSAssert1(0, @”Failed to open database with msg ‘%@’”, sqlite3_errmsg(dbHandle));
}