Hi, I'm Sam.

I like to create software, make music, and write about technology.

Find out more about me.

#cocoa Posts

Archiving NSManagedObject with NSCoding

Posted in cocoa, core-data, and development

Several of the apps I have been working on lately have been using Core Data. Core Data is pretty sweet. So far, I really like it.

I needed to persist an array of NSManagedObjects to NSUserDefaults to persist the state of the application between launches. Obviously, I could have done this with another attribute on the Core Data entity, but this approach seemed a lot simpler. I was surprised that NSManagedObject didn't conform to NSCoding. I guess that makes sense because if you store any custom types in your entity, it wouldn't know how to archive them. In my case, (and I would assume most others) I didn't want to archive the entire object since it was already store in Core Data. I just needed to store the object ID.

This was actually really easy. See:

//
//  SSManagedObject.h
//  Archiving NSManagedObject with NSCoding
//
//  Created by Sam Soffes on 2/28/10.
//  Copyright 2010 Sam Soffes. All rights reserved.
//

extern NSString *kURIRepresentationKey;

@interface SSManagedObject : NSManagedObject <NSCoding> {

}

@end
//
//  SSManagedObject.m
//  Archiving NSManagedObject with NSCoding
//
//  Created by Sam Soffes on 2/28/10.
//  Copyright 2010 Sam Soffes. All rights reserved.
//

#import "SSManagedObject.h"
#import "AppDelegate.h"

NSString *kURIRepresentationKey = @"URIRepresentation";

@implementation SSManagedObject

- (id)initWithCoder:(NSCoder *)decoder {
    AppDelegate *appDelegate = [AppDelegate sharedAppDelegate];
    NSPersistentStoreCoordinator *psc = [appDelegate persistentStoreCoordinator];
    NSManagedObjectContext *context = [appDelegate managedObjectContext];
    self = (SSManagedObject *)[[context objectWithID:[psc managedObjectIDForURIRepresentation:(NSURL *)[decoder decodeObjectForKey:kURIRepresentationKey]]] retain];
    return self;
}


- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:[[self objectID] URIRepresentation] forKey:kURIRepresentationKey];
}

@end

I always add + (id)sharedAppDelegate to my application delegate to save some typing:

+ (AppDelegate *)sharedAppDelegate {
    return (AppDelegate *)[[UIApplication sharedApplication] delegate];
}

Pretty simple right? Then you can do something like this to archive your objects:

[[[AppDelegate sharedAppDelegate] managedObjectContext] save:nil];
NSData *archivedObjects = [NSKeyedArchiver archivedDataWithRootObject:objects];

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:archivedObjects forKey:@"someKey"];
[userDefaults synchronize];

Unarchiving is also super easy:

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSData *objectsData = [userDefaults objectForKey:@"someKey"];
if ([objectsData length] > 0) {
    NSArray *objects = [NSKeyedUnarchiver unarchiveObjectWithData:objectsData]];
}

Clean Up Your Project

Posted in cocoa, development, ios, rake, ruby, and scribd

Many of the apps I work on are usually 100% custom. There is rarely any system UI components visible to the user. Styling the crap out of apps like this makes for tons of images in my iOS projects to get everything the way the designer wants. I'm starting to drawRect: stuff more these days because it makes it easier to reuse, but anyway.

There are literally hundreds of images in the Scribd app I've been working on. Designers changing their mind plus everything custom leaves a lot of images behind that are no longer used. Our application was starting to be several megs and a lot of it was unused images. So... being the programmer I am, I wrote a script.

desc 'Remove unused images'
task :clean_assets do
  require 'set'

  all = Set.new
  used = Set.new
  unused = Set.new

  # White list
  used.merge %w{Icon Icon-29 Icon-50 Icon-58 Icon-72 Icon-114}

  regex = /\[UIImage imageNamed:@"([a-zA-Z0-9\-_]+).png"\]/
  Dir.glob('Classes/*.m').each do |path|
    used.merge File.open(path).read.scan(regex).flatten
  end

  Dir.glob('Resources/Images/*.png').each do |path|
    next if path.include? '@2x.png'
    all << path.gsub(/Resources\/Images\/([a-zA-Z0-9\-_]+).png/, "\\1")
  end

  unused = all - used
  unused.each do |key|
    `rm -f Resources/Images/#{key}.png Resources/Images/#{key}@2x.png`
  end

  puts "#{all.length} total found"
  puts "#{used.length} used found"
  puts "#{unused.length} deleted"
end

It basically searches all of your source files for references for [UIImage imageWithName:@"image_name_here"]. Then it looks at all of the images on disk and removes any you didn't reference. I setup a whitelist for icons and other images I don't reference directly. You might need to tweak the paths a bit to work for your setup.

Hopefully this little rake task helps someone clean up their project too.

Parsing JSON with the iPhone's Private JSON Framework

Posted in cocoa, development, iphone, and json

So my post on Cocoa web services got a lot of attention when Gruber linked me the other day. This started a conversation on Twitter and basically ended up landing on using binary property lists over JSON for size and parsing, but that's for another blog post.

All of this got me thinking about how Apple does their JSON parsing, since they are obviously using JSON in several of the built in apps. I happened to notice that there was a JSON.framework in the Private Frameworks folder this evening. I tried class-dumping it and it surprising worked! (Most of the other private frameworks I tried to class-dump didn't produce any results.)

I figured what the heck, might as well try it. It turns out that it was really easy to implement. I posted a sample project demonstrating this on GitHub.

It really sucks that this is private. It works really well. I wish Apple would open this up. I know a lot of people would benefit from it. I've heard that if you link against any private frameworks in your app, it will automatically get rejected. I haven't been brave enough to test this yet.

Anyway, it's kinda a cool discovery. Check out my sample project on GitHub.