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
@macdrevx
Copy link

@jnjosh You should check the context again on line 78 or do the context check separately.

@macdrevx
Copy link

@jnjosh Also, what do you think about changing the callbacks so they pass the observed object and the entire change dictionary? This way in cases where you wanted to, you could easily use the same callback for a collection of the same kind of objects.

@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