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]];
}
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.
Here's how I finally got Compass working with Rails 3.1rc4 (with includes, sprites, etc).
Gemfile excerpt:
gem 'sass-rails', :git => 'https://github.com/rails/sass-rails.git', :ref => '031236b31eaf20658226a9ae051749cc6647c33f'
gem 'compass', :git => 'https://github.com/chriseppstein/compass.git', :ref => '2c1fcfcad708875d10db65740aabf417abc636a6'
gem 'sprockets', '2.0.0.beta.10'
config/compass.rb excerpt:
http_images_path = '/assets/'
That's it. You don't have to do any of the crazy initializer hacks or anything like that. (If you were doing that, you can remove it all). The rest of my Gemfile and compass.rb are just standard stuff. Here's my full compass.rb if that helps though.
The only other thing you'll need to do is be sure all of your stylesheets are in app/assets/stylesheets/ and end in .css.scss or .css.sass. Eventually you won't have to include the .css part if you don't want to, but for now, it's required (due to Sprockets).
Big thanks to Jon McCartie and
Chris Eppstein for all of their help!
Update: Rails 3.1.0.rc5
Rails 3.1.0.rc5 just came out. Here's the updates for rc5.
Gemfile excerpt:
gem 'sass-rails', :git => 'https://github.com/rails/sass-rails.git', :ref => '231b14da040c3ad320076cbaaa70190d14b95d37'
gem 'compass', :git => 'https://github.com/chriseppstein/compass.git', :ref => '33263caffe5548a64253976c0a034afe1ed567f4'
That's it! Your compass.rb remains the same as before with http_images_path. You don't need to add .css to partials' file names any more now!