Monday, March 22, 2010

Absolute Filenames

Well, I got bit today by something I should've known better than to do. It was a classic newbie mistake so, but it caused me enough grief that I feel it's worth embarrassing myself with a cautionary post. Here's what happened:

When you use Core Data, Apple recommends that except for relatively small chunks of binary data, you store binary data like images, sounds, and movies in the file system and not inside Core Data's persistent store. Now, in my experience, SQLite can handle even really large blobs without too much additional overhead, but when Apple makes a specific recommendation like this, I find it's usually a good idea to follow that recommendation unless I have a really good reason not to. In this case, I didn't.

In an app I'm working on for a client, I'm recording audio using AVAudioRecorder. I don't ask the user to choose a name for the file, I just store the recordings as caf files in the file system using a filename based on the date and time, like so:
NSString *audioPathString = [NSString stringWithFormat:@"%@/%@", DOCUMENTS_FOLDER, [item valueForKey:@"audioPath"]];


Then, later on, after the recording is done, I store audioPathString inside a managed object that represents audio recordings:

audioItem.audioPath = audioPathString;


Then, when I want to play the sound, I just pull the pathname from Core Data and load the data from the file system, sort of like this:

NSError *error;

NSURL *url = [NSURL fileURLWithPath:item.audioPath];
AVAudioPlayer *player =[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];


And it worked great. For a while. I could quit and start back up and it would keep working. And then, at some point, it wouldn't work any more. It would report back OSStatus codes of -43 or -50 when I tried to create an instance of AVAudioPlayer, even though the audio file was still in the /Documents directory. I'm sure a few of you are chuckling to yourselves about my dumb error, but for those who don't see it, I'll explain.

Tip: If you find yourself in a situation where an NSError gives you nothing more than an OSStatus or OSErr error code, you can find out what that error code means by dropping to the terminal and typing macerror followed by the error code you're interested in, including the - (these old-style error codes are negative numbers).


The full pathname to an item in the Documents directory of an application's sandbox includes not the name of the application, but a UUID (unique identifier) instead. When you build and run your application, a new UUID is calculated if, and only if, something has changed since your last build. If you haven't made any changes, it keeps running with the previous UUID. Once you make some changes to code or a resource and re-compile, the UUID changes, and the stored absolute path no longer points to the right location. The portion of the URL that represents the Application's UUID has now changed. Xcode is smart enough to move the application's /Documents directory to the new location so you don't lose all of your data while developing, but all that data is now at a new location in the file system.

So, how do you handle this situation? The easiest way is to just store the filename and then re-build the absolute path at runtime by assuming the file is in your application's /Documents directory. So, instead of storing the path from recorderFilePath as I did in the earlier code sample, you would store just the last path component using NSString's lastPathComponent method, like so:

newItem.audioPath = [recorderFilePath lastPathComponent];


Then, when it's time to load the file, you just re-build the full path to the file based on the current UUID of the application, like so:

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *audioPathString = [NSString stringWithFormat:@"%@/%@", documentsDirectory, [item valueForKey:@"audioPath"]];


Using this approach, your code will continue to function even between development builds or application releases, and you won't waste an hour trying to figure out why your perfectly valid files won't load.



12 comments:

sergiutruta said...

Thanks for sharing this!

Malcolm Hall said...

look up relative filenames ;-)

mjl said...

I guess for path manipulations you'd rather use stringByAppendingPathComponent instead of manually building it, shouldn't you?

Jeff LaMarche said...

Malcolm Hall:

Relative filenames were my first thought, but are too fragile. Apple could change the bundle layout, for example (default path is deep inside app bundle where executable is). Using the NSSearchPathForDirectoriesInDomains is really the way to go.

mjl:

Yes, that's the safest way of doing it, though that method is really a throwback to the days when NeXTSTEP ran both on Unix variants and on Windows. Apple seems unlikely to move away from Unix underpinnings any time soon so my approach should work fine, but for sure, I probably should've used the "safer" stringByAppendingPathComponent: method. Good catch. I'll try and update the posting when I get a chance.

Michael said...

Jeff, It really makes me feel better than someone I view as a "hero" of coding also makes mistakes (so it isn't just me then). Being a relative newbie I had no background knowledge so had not choice but to use Apple's approach, so I too use stringByAppendingPathComponent.

Thanks, Michael.

Jeff LaMarche said...

Michael:

You never stop making mistakes unless you stop learning :)

In reality, I probably make more than my share of mistakes, and I tend to make them more publicly than most. :)

Jeff

Rose said...

Hi,

Your blog is awesome, nice and informative. I hope our services will be helpful for your visitors. Our services include good, quality & professional web design services, website development, data entry, windows mobile application development, web research, data entry, logo and branding.

Mark said...

I visited your blog while i was searching on Google. I really appreciate your blog and it's content. Also it has very good information of web design and development. I would also like you to visit my site and give valuable suggestions about my website. Please make your choice for me also by using this link. Miami Web Development

Mark said...

I visited your blog while i was searching on Google. I really appreciate your blog and it's content. Also it has very good information of web design and development. I would also like you to visit my site and give valuable suggestions about my website. Please make your choice for me also by using this link. Web Development new york

JeansPilot said...

JeansPilot offers the chance to buy a large variety of men’s and women’s jeans clothing from the world famous Italian Brands.
Online jeans clothing store looks for original fashion clothing sales and clearances of worldwide known designers. We participate in fashion auctions to get the lowest possible price for Top quality Clothes, Shoes and Accessories.
Buy Jeans

SEO Services Consultants said...

Nice information, many thanks to the author. It is incomprehensible to me now, but in general, the usefulness and significance is overwhelming. Thanks again and good luck! Web Design Company

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels