Skip to content

Instantly share code, notes, and snippets.

@jnjosh
Last active March 23, 2017 20:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jnjosh/6968514 to your computer and use it in GitHub Desktop.
Save jnjosh/6968514 to your computer and use it in GitHub Desktop.
Reading http://chris.eidhof.nl/post/63590250009/lightweight-key-value-observing I got curious about using an observer object for KVO. It is an interesting idea.
//
// JNJObserver.h
//
// Created by Josh Johnson on 10/10/13.
// Copyright (c) 2013 jnjosh.com. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef void(^JNJObserverActionHandler)(id changingObject, id oldValue, id newValue);
@interface JNJObserver : NSObject
+ (instancetype)observerWithObject:(id)object
keyPath:(NSString *)keyPath
actionHandler:(JNJObserverActionHandler)actionHandler;
+ (instancetype)observerWithObject:(id)object
keyPath:(NSString *)keyPath
target:(id)target
action:(SEL)action;
@end
//
// JNJObserver.m
//
// Created by Josh Johnson on 10/10/13.
// Copyright (c) 2013 jnjosh.com. All rights reserved.
//
#import "JNJObserver.h"
@interface JNJObserver ()
@property (nonatomic, weak) id object;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL action;
@property (nonatomic, copy) JNJObserverActionHandler actionHandler;
@end
@implementation JNJObserver
#pragma mark - Convenience Methods
+ (instancetype)observerWithObject:(id)object keyPath:(NSString *)keyPath actionHandler:(JNJObserverActionHandler)actionHandler
{
return [[JNJObserver alloc] initWithObject:object keyPath:keyPath actionHandler:actionHandler];
}
+ (instancetype)observerWithObject:(id)object keyPath:(NSString *)keyPath target:(id)target action:(SEL)action
{
return [[JNJObserver alloc] initWithObject:object keyPath:keyPath target:target action:action];
}
#pragma mark - Life Cycle
- (id)initWithObject:(id)object keyPath:(NSString *)keyPath actionHandler:(JNJObserverActionHandler)actionHandler
{
if (self = [super init]) {
self.object = object;
self.keyPath = keyPath;
self.actionHandler = actionHandler;
[object addObserver:self
forKeyPath:keyPath
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:(__bridge void *)self];
}
return self;
}
- (id)initWithObject:(id)object keyPath:(NSString *)keyPath target:(id)target action:(SEL)action
{
if (self = [super init]) {
self.object = object;
self.keyPath = keyPath;
self.target = target;
self.action = action;
[self.object addObserver:self
forKeyPath:self.keyPath
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:(__bridge void *)self];
}
return self;
}
- (void)dealloc
{
[_object removeObserver:self forKeyPath:_keyPath context:(__bridge void *)self];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == (__bridge void *)self) {
if (self.actionHandler) {
self.actionHandler(object, change[ NSKeyValueChangeOldKey ], change[ NSKeyValueChangeNewKey ] );
}
else if ([self.target respondsToSelector:self.action]) {
NSNumber *oldValue = change[ NSKeyValueChangeOldKey ];
NSNumber *newValue = change[ NSKeyValueChangeNewKey ] ;
NSMethodSignature *methodSignature = [self.target methodSignatureForSelector:self.action];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.target = self.target;
invocation.selector = self.action;
if (methodSignature.numberOfArguments >= 3) {
[invocation setArgument:&object atIndex:2];
}
if (methodSignature.numberOfArguments >= 4) {
[invocation setArgument:&oldValue atIndex:3];
}
if (methodSignature.numberOfArguments >= 5) {
[invocation setArgument:&newValue atIndex:4];
}
[invocation invoke];
}
}
}
@end
@jnjosh
Copy link
Author

jnjosh commented Oct 15, 2013

@macdrevx Thanks for the catch on the context check. I don't mind the callback with the collection but I kind of prefer the explicit callback stating the object it is giving. What do you think of adding it as a third class helper?

Also I was thinking that it would be cool to add the ability to create multiple observation to the observer. What do you think of that?

@jnjosh
Copy link
Author

jnjosh commented Oct 16, 2013

@macdrevx Updated to force object to pass along with the old/new value. The signatures are a little underdefined though so I'm not sure I like it like this now. Might make sense to document the signature as always object:oldValue:newValue in that order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment