Today I'm open-sourcing a small project I use to do remote image downloading and caching on iOS called JMImageCache. It uses an NSCache based remote-image caching mechanism that can be easily integrated into any project. It is very light weight and isn't meant to be a replacement for UIImageView or anything like that. Just drop it into your project, grab a reference to it and start using it where ever it makes sense.
How It Works (Logically)
There are three states an image can be in:
- Cached In Memory
- Cached On Disk
- Not Cached
If an image is requested from cache, and it has never been cached, it is downloaded, stored on disk, put into memory and returned via a delegate callback.
If an image is requested from cache, and it has been cached, but hasn't been requested this session, it is read from disk, brought into memory and returned immediately.
If an image is requested from cache, and it has been cached and it is already in memory, it is simply returned immediately.
The idea behind JMImageCache is to always return images the fastest way possible, thus the in-memory caching. Reading from disk can be expensive, and should only be done if it has to be.
How It Works (Code)
Request an image like so:
UIImage *mScott = [[JMImageCache sharedCache] imageForURL:@"http://dundermifflin.com/i/MichaelScott.png" delegate:self];
imageForURL:delegate: will return either a UIImage object, or nil. If it returns nil, then that means the image needs to be downloaded.
If the image needs to be downloaded, you'll be notified via a callback to the delegate object you specified in imageForURL:delegate::
- (void) cache:(JMImageCache *)c didDownloadImage:(UIImage *)i forURL:(NSString *)url {
NSLog(@"Downloaded (And Cached) Image From URL: %@", url);
}
Clearing The Cache
The beauty of building on top of NSCache is thatJMImageCache handles low memory situations gracefully. It will evict objects on its own when memory gets tight, you don't need to worry about it.
However, if you really need to, clearing the cache manually is this simple:
[[JMImageCache sharedCache] removeAllObjects];
If you'd like to remove a specific image from the cache, you can do this:
[[JMImageCache sharedCache] removeImageForURL:@"http://dundermifflin.com/i/MichaelScott.png"];
Demo App
The Github repository includes a demo project. Just a simple UITableViewController app that loads a few images. It does so by calling imageForURL:delegate: and passing in the UITableViewCell as the JMImageCacheDelegate to respond to when an image is downloaded. Then we simply set the image of the cell's imageView and then call setNeedsLayout on the cell to refresh the view. Nothing too fancy, but it should give you a good idea of a standard usage of JMImageCache.
Using JMImageCache In Your App
All you need to do is copy JMImageCache.h and JMImageCache.m into your project, #import the header where you need it, and start using it.
Notes
JMImageCache purposefully uses NSString objects instead of NSURL's to make things easier and cut down on [NSURL URLWithString:@"..."] bits everywhere. Just something to notice in case you see any strange EXC_BAD_ACCESS exceptions, make sure you're passing in NSString's and not NSURL's.
I've just released a new little helper library for iOS development. It's called JMWhenTapped. It's is a simple little syntactical-sugar addition to all UIView objects, as well as any class that inherits from UIView. It allows you to assign touch-up, touch-down, and tapped (touched down then up) actions to a UIView object using a convenient blocks-style syntax.
Installation
Clone the repo. Add the JMWhenTapped folder to your iOS 4 project. #import "JMWhenTapped.h" wherever you'd like to use the syntax.
Examples & Usage
Use it like this:
[myView whenTapped:^{
NSLog(@"I was tapped!");
}];
Or like this:
[myView whenTouchedDown:^{
NSLog(@"I was touched down!");
}];
And also like this:
[myView whenTouchedUp:^{
NSLog(@"I was touched up!");
}];
The Different Actions
The whenTapped: method should be used in cases where you simply want something to happen when the user taps on a view (i.e. you are concerned with performing some action when their finger is down then up, like changing to a "pressed" state.)
The whenTouchedDown: method should be used when you want to trigger some action when the user touches down on your view.
The whenTouchedUp: method should be used when you want to trigger some action when the user touches up on your view.
Demo
Included in the repo is a demo Xcode project that illustrates a quick example of how to use JMWhenTapped.
SNRFetchedResultsController is a "port" (not exactly) of NSFetchedResultsController from iOS to OS X. It is not a drop in replacement for NSFetchedResultsController, but performs many of the same tasks in relation to managing results from a Core Data fetch and notifying a delegate when objects are inserted, deleted, updated, or moved in order to update the UI.
As someone who is primarily an iOS guy, having this class around makes the thought of doing some larger Mac OS X and AppKit-based stuff much more inviting.
Implementing it is this easy:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Car" inManagedObjectContext:context];
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:@"year" ascending:YES], nil];
request.predicate = [NSPredicate predicateWithFormat:@"wheels.@count != 0"];
request.fetchBatchSize = 20;
self.fetchedResultsController = [[SNRFetchedResultsController alloc] initWithManagedObjectContext:context fetchRequest:request];
self.fetchedResultsController.delegate = self;
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
if (error) {
NSLog(@"Unresolved error: %@ %@", error, [error userInfo]);
}
- (NSInteger) numberOfRowsInTableView:(NSTableView *)aTableView
{
return [self.fetchedResultsController count];
}
- (id) tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
return [self.fetchedResultsController objectAtIndex:rowIndex];
}
- (void) controller:(SNRFetchedResultsController *)controller didChangeObject:(id)anObject atIndex:(NSUInteger)index forChangeType:(SNRFetchedResultsChangeType)type newIndex:(NSUInteger)newIndex
{
switch (type) {
case SNRFetchedResultsChangeDelete:
[self.tableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:index] withAnimation:NSTableViewAnimationSlideLeft];
break;
case SNRFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:newIndex] withAnimation:NSTableViewAnimationSlideDown];
break;
case SNRFetchedResultsChangeUpdate:
[self.tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:index] columnIndexes:[NSIndexSet indexSetWithIndex:0]];
break;
case SNRFetchedResultsChangeMove:
[self.tableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:index] withAnimation:NSTableViewAnimationSlideLeft];
[self.tableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:newIndex] withAnimation:NSTableViewAnimationSlideDown];
break;
default:
break;
}
}
SNRFetchedResultsController is created by Indragie Karunaratne. He has been rocking the Mac OS X open source stuff lately and this latest one doesn't disappoint.