Aleksandar • Vacić

iOS bits and pieces

Never save absolute file paths in your iOS app

There’s a mistake I make every 6 months or so and I hope writing a blog post about it will finally engrave it into the back of my mind.

Have you seen how the file paths look like for the files in your iOS app’s little sandbox? Something like this:

1
/var/mobile/Applications/F71BA910-A1F0-4B39-85CB-775806ACFF62/Documents/orders/1832006/1.pdf

The mistake I make is that I take this full URL and then save it into to Core Data storage or wherever (in this case, as order file path). And this will work if you never make updates to the app. However, if you do publish an update to the app, this URL will not be valid anymore.

The gibberish bit in the middle - F71BA910-A1F0-4B39-85CB-775806ACFF62 - is path that is specific to a given app version. So, when you update, your new app version will get some other string in there and all your previously saved / hard-coded URLs will be useless.

What you need to save is only the part that you control and maintain - in my case that’s orders/1832006/1.pdf. Which excludes the Documents part as well as this is automatically created by iOS for your app. Documents is one of several app directories that you can and should fetch using the provided iOS APIs. There is nothing that guaranties that in some future iOS versions Apple won’t rename Documents to something else, so you should not think it’s there to stay.

So, what’s the proper way to read/save local path files?

A single bug in my StoreKit code that lost me 90% of IAP sales

In Banca, my very successful currency conversion app for iPhone, I added ability to buy various themes, through in-app purchase. I followed the guide, implemented all the bits and pieces of the whole workflow. Apart from one important part.

My theme store is modal view controller and in its init method I got this:

1
2
3
4
5
6
7
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  if (self) {
      if ([SKPaymentQueue canMakePayments]) {
          // set observer, to follow transactions processing
          [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
      }

The trouble was, I forgot to add this:

1
2
3
4
5
- (void)dealloc {
  if ([SKPaymentQueue canMakePayments]) {
      [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
  }
}

Tip: revert pngcrush optimization in Xcode 4.3

When you are adding PNG images to an iOS app, Xcode will optimize and compress them when compiling, using a tool called pngcrush. To revert this optimization back, copy the .png files from the .ipa bundle into some folder and run this in terminal

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush -dir SOME_DIR -revert-iphone-optimizations -q *.png

SOME_DIR is a destination path, something like ~/temp.