Skip to content

Commit

Permalink
Merge pull request #23 from sixten/develop
Browse files Browse the repository at this point in the history
Use KVO in a context-aware fashion.
  • Loading branch information
artgillespie committed Aug 2, 2013
2 parents ffde0f3 + e1a3f03 commit 367c259
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 119 deletions.
12 changes: 6 additions & 6 deletions OGImage/OGImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@

/**
* Convenience method: This is equivalent to calling
* [ogimg addObserver:observer forKeyPath:@"image" options:NSKeyValueObservingOptionNew context:nil];
* [ogimg addObserver:observer forKeyPath:@"error" options:NSKeyValueObservingOptionNew context:nil];
* [ogimg addObserver:observer forKeyPath:@"image" options:NSKeyValueObservingOptionNew context:context];
* [ogimg addObserver:observer forKeyPath:@"error" options:NSKeyValueObservingOptionNew context:context];
*/
- (void)addObserver:(NSObject *)observer;
- (void)addObserver:(NSObject *)observer context:(void *)context;

/**
* Convenience method: This is equivalent to calling
* [ogimg removeObserver:observer forKeyPath:@"image"];
* [ogimg removeObserver:observer forKeyPath:@"error"];
* [ogimg removeObserver:observer forKeyPath:@"image" context:context];
* [ogimg removeObserver:observer forKeyPath:@"error" context:context];
*/
- (void)removeObserver:(NSObject *)observer;
- (void)removeObserver:(NSObject *)observer context:(void *)context;

/**
* Subclasses can override this method to perform caching, processing, etc., but
Expand Down
12 changes: 6 additions & 6 deletions OGImage/OGImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ - (id)initWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage {
return self;
}

- (void)addObserver:(NSObject *)observer {
[self addObserver:observer forKeyPath:@"image" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:observer forKeyPath:@"error" options:NSKeyValueObservingOptionNew context:nil];
- (void)addObserver:(NSObject *)observer context:(void *)context {
[self addObserver:observer forKeyPath:@"image" options:NSKeyValueObservingOptionNew context:context];
[self addObserver:observer forKeyPath:@"error" options:NSKeyValueObservingOptionNew context:context];
}

- (void)removeObserver:(NSObject *)observer {
- (void)removeObserver:(NSObject *)observer context:(void *)context {
@try {
[self removeObserver:observer forKeyPath:@"image"];
[self removeObserver:observer forKeyPath:@"image" context:context];
} @catch (NSException *e) {}
@try {
[self removeObserver:observer forKeyPath:@"error"];
[self removeObserver:observer forKeyPath:@"error" context:context];
} @catch (NSException *e) {}
}

Expand Down
4 changes: 2 additions & 2 deletions OGImage/OGScaledImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@
/**
* Equivalent to adding an observer for @"image", @"scaledImage", & @"error"
*/
- (void)addObserver:(NSObject *)observer;
- (void)addObserver:(NSObject *)observer context:(void *)context;
/**
* Equivalent to removing an observer for @"image", @"scaledImage", & @"error"
*/
- (void)removeObserver:(NSObject *)observer;
- (void)removeObserver:(NSObject *)observer context:(void *)context;

/**
* The scaled image—The inherited `image` property is set to the full-size image at `url`.
Expand Down
12 changes: 6 additions & 6 deletions OGImage/OGScaledImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ - (id)initWithImage:(__OGImage *)image size:(CGSize)size cornerRadius:(CGFloat)c
return self;
}

- (void)addObserver:(NSObject *)observer {
[super addObserver:observer];
[self addObserver:observer forKeyPath:@"scaledImage" options:NSKeyValueObservingOptionNew context:nil];
- (void)addObserver:(NSObject *)observer context:(void *)context {
[super addObserver:observer context:context];
[self addObserver:observer forKeyPath:@"scaledImage" options:NSKeyValueObservingOptionNew context:context];
}

- (void)removeObserver:(NSObject *)observer {
[super removeObserver:observer];
[self removeObserver:observer forKeyPath:@"scaledImage"];
- (void)removeObserver:(NSObject *)observer context:(void *)context {
[super removeObserver:observer context:context];
[self removeObserver:observer forKeyPath:@"scaledImage" context:context];
}

- (void)loadImageFromURL {
Expand Down
33 changes: 20 additions & 13 deletions OGImageDemo/OGImageDemo/OGImageTableViewCell.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#import "OGImageTableViewCell.h"
#import "OGScaledImage.h"

static NSString *KVOContext = @"OGImageTableViewCell observation";

@implementation OGImageTableViewCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
Expand All @@ -25,13 +27,18 @@ - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
#pragma mark - KVO

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSAssert(YES == [NSThread isMainThread], @"KVO fired on thread other than main...");
if ([keyPath isEqualToString:@"scaledImage"]) {
self.imageView.image = self.image.scaledImage;
self.textLabel.text = [[self.image.url path] lastPathComponent];
self.detailTextLabel.text = [NSString stringWithFormat:@"%.2f", self.image.loadTime];
} else if ([keyPath isEqualToString:@"error"]) {

if( (void *)&KVOContext == context ) {
NSAssert(YES == [NSThread isMainThread], @"KVO fired on thread other than main...");
if ([keyPath isEqualToString:@"scaledImage"]) {
self.imageView.image = self.image.scaledImage;
self.textLabel.text = [[self.image.url path] lastPathComponent];
self.detailTextLabel.text = [NSString stringWithFormat:@"%.2f", self.image.loadTime];
} else if ([keyPath isEqualToString:@"error"]) {

}
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

Expand All @@ -42,19 +49,19 @@ - (void)setImage:(OGScaledImage *)image {
* When the cell's image is set, we want to first make sure we're no longer listening
* for any KVO notifications on the cell's previous image.
*/
[_image removeObserver:self forKeyPath:@"error"];
[_image removeObserver:self forKeyPath:@"scaledImage"];
[_image removeObserver:self forKeyPath:@"error" context:&KVOContext];
[_image removeObserver:self forKeyPath:@"scaledImage" context:&KVOContext];
_image = image;
self.imageView.image = _image.scaledImage;
self.textLabel.text = [[self.image.url path] lastPathComponent];
self.detailTextLabel.text = NSLocalizedString(@"Loading", @"");
[_image addObserver:self forKeyPath:@"error" options:NSKeyValueObservingOptionNew context:nil];
[_image addObserver:self forKeyPath:@"scaledImage" options:NSKeyValueObservingOptionNew context:nil];
[_image addObserver:self forKeyPath:@"error" options:NSKeyValueObservingOptionNew context:&KVOContext];
[_image addObserver:self forKeyPath:@"scaledImage" options:NSKeyValueObservingOptionNew context:&KVOContext];
}

- (void)dealloc {
[_image removeObserver:self forKeyPath:@"error"];
[_image removeObserver:self forKeyPath:@"scaledImage"];
[_image removeObserver:self forKeyPath:@"error" context:&KVOContext];
[_image removeObserver:self forKeyPath:@"scaledImage" context:&KVOContext];
}

@end
31 changes: 19 additions & 12 deletions OGImageDemo/OGImageTests/OGImageAssetsLibraryTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

#import <AssetsLibrary/AssetsLibrary.h>

static NSString *KVOContext = @"OGImageAssetsLibraryTests observation";

static CGSize const OGExpectedSize = {1024.f, 768.f};

@interface OGImageAssetsLibraryTests : GHAsyncTestCase
Expand Down Expand Up @@ -44,28 +46,33 @@ - (void)setUp {
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"image"]) {
OGCachedImage *image = (OGCachedImage *)object;
if (nil != image.image) {
GHAssertTrue(CGSizeEqualToSize(OGExpectedSize, image.image.size), @"Expected image of size %@, got %@", NSStringFromCGSize(OGExpectedSize), NSStringFromCGSize(image.image.size));
[self notify:kGHUnitWaitStatusSuccess];
} else {
if ((void *)&KVOContext == context) {
if ([keyPath isEqualToString:@"image"]) {
OGCachedImage *image = (OGCachedImage *)object;
if (nil != image.image) {
GHAssertTrue(CGSizeEqualToSize(OGExpectedSize, image.image.size), @"Expected image of size %@, got %@", NSStringFromCGSize(OGExpectedSize), NSStringFromCGSize(image.image.size));
[self notify:kGHUnitWaitStatusSuccess];
} else {
[self notify:kGHUnitWaitStatusFailure];
}
} else if ([keyPath isEqualToString:@"error"]) {
OGCachedImage *image = (OGCachedImage *)object;
GHFail(@"Got error loading OGCachedImage: %@", image.error);
[self notify:kGHUnitWaitStatusFailure];
}
} else if ([keyPath isEqualToString:@"error"]) {
OGCachedImage *image = (OGCachedImage *)object;
GHFail(@"Got error loading OGCachedImage: %@", image.error);
[self notify:kGHUnitWaitStatusFailure];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

- (void)testAssetsLibrary {
[self prepare];
GHAssertNotNil(_assetURL, @"Expect _assetURL to be populated by setUp");
OGCachedImage *image = [[OGCachedImage alloc] initWithURL:_assetURL key:nil];
[image addObserver:self];
[image addObserver:self context:&KVOContext];
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.];
[image removeObserver:self];
[image removeObserver:self context:&KVOContext];
}

@end
62 changes: 37 additions & 25 deletions OGImageDemo/OGImageTests/OGImageAsyncTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#import "OGImage.h"
#import "OGImageLoader.h"

static NSString *KVOContext = @"OGImageAsyncTests observation";

static NSString * const TEST_IMAGE_URL_STRING = @"http://easyquestion.net/thinkagain/wp-content/uploads/2009/05/james-bond.jpg";
static NSString * const FAKE_IMAGE_URL_STRING = @"http://easyquestion.net/thinkagain/wp-content/uploads/2009/05/james00.jpg";
static const CGSize TEST_IMAGE_SIZE = {317.f, 400.f};
Expand All @@ -22,26 +24,31 @@ @interface OGImage404Test : GHAsyncTestCase
@implementation OGImage404Test

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSAssert(YES == [NSThread isMainThread], @"Expected `observeValueForKeyPath` to only be called on main thread");
if ([keyPath isEqualToString:@"error"]) {
OGImage *image = (OGImage *)object;
GHTestLog(@"Error changed: %@", image.error);
if (OGImageLoadingError == image.error.code) {
// we expect a loading error here
[self notify:kGHUnitWaitStatusSuccess];
} else {
[self notify:kGHUnitWaitStatusFailure];
if ((void *)&KVOContext == context) {
NSAssert(YES == [NSThread isMainThread], @"Expected `observeValueForKeyPath` to only be called on main thread");
if ([keyPath isEqualToString:@"error"]) {
OGImage *image = (OGImage *)object;
GHTestLog(@"Error changed: %@", image.error);
if (OGImageLoadingError == image.error.code) {
// we expect a loading error here
[self notify:kGHUnitWaitStatusSuccess];
} else {
[self notify:kGHUnitWaitStatusFailure];
}
return;
}
return;
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

- (void)test404 {
[self prepare];
OGImage *image = [[OGImage alloc] initWithURL:[NSURL URLWithString:FAKE_IMAGE_URL_STRING]];
[image addObserver:self];
[image addObserver:self context:&KVOContext];
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.];
[image removeObserver:self];
[image removeObserver:self context:&KVOContext];
}

@end
Expand All @@ -54,27 +61,32 @@ @interface OGImageTest1 : GHAsyncTestCase

@implementation OGImageTest1
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSAssert(YES == [NSThread isMainThread], @"Expected `observeValueForKeyPath` to only be called on main thread");
if ([keyPath isEqualToString:@"image"]) {
OGImage *image = (OGImage *)object;
GHTestLog(@"Image loaded: %@ : %@", image.image, NSStringFromCGSize(image.image.size));
if (NO == CGSizeEqualToSize(image.image.size, TEST_IMAGE_SIZE)) {
[self notify:kGHUnitWaitStatusFailure];
} else {
[self notify:kGHUnitWaitStatusSuccess];
if ((void *)&KVOContext == context) {
NSAssert(YES == [NSThread isMainThread], @"Expected `observeValueForKeyPath` to only be called on main thread");
if ([keyPath isEqualToString:@"image"]) {
OGImage *image = (OGImage *)object;
GHTestLog(@"Image loaded: %@ : %@", image.image, NSStringFromCGSize(image.image.size));
if (NO == CGSizeEqualToSize(image.image.size, TEST_IMAGE_SIZE)) {
[self notify:kGHUnitWaitStatusFailure];
} else {
[self notify:kGHUnitWaitStatusSuccess];
}
return;
}
return;
GHTestLog(@"Unexpected key change...");
[self notify:kGHUnitWaitStatusFailure];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
GHTestLog(@"Unexpected key change...");
[self notify:kGHUnitWaitStatusFailure];
}

- (void)testImageOne {
[self prepare];
OGImage *image = [[OGImage alloc] initWithURL:[NSURL URLWithString:TEST_IMAGE_URL_STRING]];
[image addObserver:self forKeyPath:@"image" options:NSKeyValueObservingOptionNew context:nil];
[image addObserver:self forKeyPath:@"image" options:NSKeyValueObservingOptionNew context:&KVOContext];
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.];
[image removeObserver:self forKeyPath:@"image"];
[image removeObserver:self forKeyPath:@"image" context:&KVOContext];
}

@end
27 changes: 17 additions & 10 deletions OGImageDemo/OGImageTests/OGImageFileTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#import "OGCachedImage.h"
#import "OGImageCache.h"

static NSString *KVOContext = @"OGImageFileTests observation";

static CGSize const OGExpectedSize = {1024.f, 768.f};

@interface OGImageFileTests : GHAsyncTestCase
Expand All @@ -23,16 +25,21 @@ - (void)setUp {
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"image"]) {
OGCachedImage *image = (OGCachedImage *)object;
if (nil == image) {
if ((void *)&KVOContext == context) {
if ([keyPath isEqualToString:@"image"]) {
OGCachedImage *image = (OGCachedImage *)object;
if (nil == image) {
[self notify:kGHUnitWaitStatusFailure];
} else {
GHAssertTrue(CGSizeEqualToSize(OGExpectedSize, image.image.size), @"Expected image of size %@, got %@", NSStringFromCGSize(OGExpectedSize), NSStringFromCGSize(image.image.size));
[self notify:kGHUnitWaitStatusSuccess];
}
} else if ([keyPath isEqualToString:@"error"]) {
[self notify:kGHUnitWaitStatusFailure];
} else {
GHAssertTrue(CGSizeEqualToSize(OGExpectedSize, image.image.size), @"Expected image of size %@, got %@", NSStringFromCGSize(OGExpectedSize), NSStringFromCGSize(image.image.size));
[self notify:kGHUnitWaitStatusSuccess];
}
} else if ([keyPath isEqualToString:@"error"]) {
[self notify:kGHUnitWaitStatusFailure];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

Expand All @@ -41,9 +48,9 @@ - (void)testFileURL {
NSURL *imageURL = [[NSBundle mainBundle] URLForResource:@"Origami" withExtension:@"jpg"];
GHAssertNotNil(imageURL, @"Couldn't get URL for test image");
OGCachedImage *image = [[OGCachedImage alloc] initWithURL:imageURL key:nil];
[image addObserver:self];
[image addObserver:self context:&KVOContext];
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.];
[image removeObserver:self];
[image removeObserver:self context:&KVOContext];
}

@end
27 changes: 17 additions & 10 deletions OGImageDemo/OGImageTests/OGImageIdempotentTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#import "GHAsyncTestCase.h"
#import "OGImage.h"

static NSString *KVOContext = @"OGImageIdempotentTests observation";

static NSString * const TEST_IMAGE_URL_STRING = @"http://easyquestion.net/thinkagain/wp-content/uploads/2009/05/james-bond.jpg";

@interface OGImageIdempotentTests : GHAsyncTestCase {
Expand All @@ -23,13 +25,18 @@ @interface OGImageIdempotentTests : GHAsyncTestCase {
@implementation OGImageIdempotentTests

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (_image1 == object) {
_image1Loaded = YES;
} else if (_image2 == object) {
_image2Loaded = YES;
if ((void *)&KVOContext == context) {
if (_image1 == object) {
_image1Loaded = YES;
} else if (_image2 == object) {
_image2Loaded = YES;
}
if (_image1Loaded && _image2Loaded) {
[self notify:kGHUnitWaitStatusSuccess];
}
}
if (_image1Loaded && _image2Loaded) {
[self notify:kGHUnitWaitStatusSuccess];
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

Expand All @@ -38,11 +45,11 @@ - (void)testIdempotent {
// a single network request with notifications
[self prepare];
_image1 = [[OGImage alloc] initWithURL:[NSURL URLWithString:TEST_IMAGE_URL_STRING]];
[_image1 addObserver:self];
[_image1 addObserver:self context:&KVOContext];
_image2 = [[OGImage alloc] initWithURL:[NSURL URLWithString:TEST_IMAGE_URL_STRING]];
[_image2 addObserver:self];
[_image2 addObserver:self context:&KVOContext];
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:15.f];
[_image1 removeObserver:self];
[_image2 removeObserver:self];
[_image1 removeObserver:self context:&KVOContext];
[_image2 removeObserver:self context:&KVOContext];
}
@end
Loading

0 comments on commit 367c259

Please sign in to comment.