A couple of weeks ago Matt Long was having a problem with an app running out of memory. He had a ginormous data file he needed to load up and process, and that memory hit was more than the app could bear. It would load just fine, into an NSData, but before he could finish with it the app would run short of memory and die.
Until recently the obvious thing would have been to tell NSData to create a memory-mapped instance. Given
NSString *path pointing to a file, you could create an NSData with almost no memory hit regardless of file size by creating it as:
NSData *data = [NSData dataWithContentsOfMappedFile:path];
Starting with iOS 5 though, this method has been deprecated. Instead, what you’re supposed to do is:
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedAlways error:&error];
So, fine, whatever, it’s a different call, so what? Well, it wasn’t working. Instruments was showing that the app was taking the full memory hit when the NSData was created. Mapping wasn’t working despite using NSDataReadingMappedAlways. So what could he do? The wheels of my mind started turning.
A few months ago I wrote here about a generic approach to safely take incoming JSON and save values to Core Data object. The goals of that code were twofold:
- Provide a safe, generic alternative to Cocoa’s
-setValuesForKeysWithDictionary:for use with NSManagedObject and its subclasses
- Handle cases where JSON data didn’t match up with what the managed objects expected. Getting a string where you expect a numeric value, or vice versa, for example, or getting a string representation of a date when you want a real NSDate object.
The first item was the most important. It’s tempting to use
-setValuesForKeysWithDictionary: to transfer JSON to a model object in one step. The method runs through the dictionary and calls
-setValue:forKey: on the target object for every entry. It has a fatal flaw though, in that it doesn’t check to see if the target object actually has a key before trying to set it. Using this method when you don’t have absolute control over the dictionary contents is an invitation to unknown key exceptions and nasty app crashes.
Fixing this for managed objects was relatively easy because Core Data provides convenient Objective-C introspection methods. The general approach was:
- Get a list of the target object’s attributes
- For each attribute, see if the incoming dictionary has an entry. If so,
- Compare the incoming type to the expected type, and convert if necessary.
-setValue:forKey:with that key and its value.
And then just last week I had the thought, wouldn’t it be nice if this worked for any object, not just for managed objects?
Hi, I’m new here. You may know me as @atomicbird on Twitter. Just a few days ago my book Core Data for iOS: Developing Data-Driven Applications for the iPad, iPhone, and iPod touch (co-written with the excellent Tim Isted) was published, and Matt invited me to contribute some Core Data tips to CIMGF. I’m going to start off discussing taking JSON data from a web service and converting it to Core Data storage. Along the way I’ll cover how to inspect managed objects to find out what attributes they have and what the attribute types are.
Publishing lead times being what they are, this post covers information not included in the book.