Skip to content

Instantly share code, notes, and snippets.

@dave-fn
Created August 31, 2016 23:29
Show Gist options
  • Save dave-fn/fd306639b2c4e74cb4c9d4ed0e3bc606 to your computer and use it in GitHub Desktop.
Save dave-fn/fd306639b2c4e74cb4c9d4ed0e3bc606 to your computer and use it in GitHub Desktop.
Objective-C block based Key-Value Observing
// *********************************************************************************************************************
// Module Name: DNKVObservation.h
// Description: Include file.
// Author Name: David F. Negrete, 2013
// *********************************************************************************************************************
#pragma mark - Type Definitions
@class DNKVObservation;
typedef void (^DNObservationBlock) (id observer, DNKVObservation *observation);
typedef NSNumber DNKVObservationID;
#pragma mark -
/** Class represents a Key Value Observation between two objects.
*
* This object encapsulates the key value observation details, such as the observed and observer objects, the key path
* being observed and the observed values.
*
* In addition, it privately stores any data required by the observation, such as whether it's currently skipping
* observations, and the block to execute.
*/
@interface DNKVObservation : NSObject
#pragma mark - Properties
/// @name Properties
/// ----------------
/// The object taking responsibility for the observation.
@property (readonly, weak, nonatomic) id observer;
/// The object being observed.
@property (readonly, weak, nonatomic) id target;
/// The key being observed.
@property (readonly, copy, nonatomic) NSString *key;
/// An identifier object.
@property (readonly, strong, nonatomic) DNKVObservationID *idNumber;
#pragma mark - Class Methods
/// @name Creation
/// --------------
/** Sets up the observation between two objects and executes a block of code when a key is updated.
* @param observer The object setting up the observation.
* @param target The object to observe.
* @param keyPath The key that will be monitored.
* @param block A block to execute when an observation notification is fired.
*/
+ (DNKVObservation*) observationByObject:(id)observer
target:(id)target
key:(NSString*)keyPath
action:(DNObservationBlock)block;
#pragma mark - Interface
/// @name Observation Change
/// ------------------------
/** Convenience method to get the new value of a key from the NSKeyValueObserving change dictionary.
* @return The new value assigned to the observed key.
*/
- (id) newValue;
/** Convenience method to get the old value of a key from the NSKeyValueObserving change dictionary.
* @return The previous value assigned to the observed key.
*/
- (id) oldValue;
/** Convenience method to get the change type of a key from the NSKeyValueObserving change dictionary.
* @return An enum value of the change type.
*/
- (NSKeyValueChange) changeType;
/** Convenience method to get the index set of a key from the NSKeyValueObserving change dictionary.
* @return The index set containing the location of updated objects within a collection.
*/
- (NSIndexSet*) changeIndexes;
@end
// *********************************************************************************************************************
// Module Name: DNKVObservation.m
// Description: Object representation of an observation KVC/KVO.
// Author Name: David F. Negrete, 2013
// *********************************************************************************************************************
#import "DNKVObservation.h"
#pragma mark Imports
#import "NSObject+DNKeyValueObserving.h"
@interface DNKVObservation ()
@property (copy, nonatomic) DNObservationBlock actionBlock;
@property (weak, nonatomic) NSDictionary *observationChange;
@property (nonatomic, getter=isObserving) BOOL observing;
@property (nonatomic, getter=isSuspended) BOOL suspended;
@end
@interface NSObject (KVOHelper)
- (DNKVObservation*) registeredObservationForTarget:(id)target key:(NSString*)keyPath;
@end
static int DNKVObservationCurrentIdentifier = 1000;
#pragma mark -
@implementation DNKVObservation
#pragma mark - Class Lifecycle
// *********************************************************************************************************************
// + observationByObject: target: key: action:
// *********************************************************************************************************************
+ (DNKVObservation*) observationByObject:(id)observer
target:(id)target
key:(NSString*)keyPath
action:(DNObservationBlock)block
{
DNKVObservation *observation = [[self alloc] initWithObject:observer
target:target
key:keyPath
action:block];
[observation startObservation];
return observation;
}
#pragma mark - Lifecycle
// *********************************************************************************************************************
// - initWithObject: target: key: action:
// *********************************************************************************************************************
- (id) initWithObject:(id)observer target:(id)target key:(NSString*)keyPath action:(DNObservationBlock)block
{
self = [super init];
if (self) {
_observer = observer;
_target = target;
_key = [keyPath copy];
_actionBlock = [block copy];
_observing = NO;
_suspended = NO;
_idNumber = @(DNKVObservationCurrentIdentifier++);
}
return self;
}
// *********************************************************************************************************************
// - dealloc
// *********************************************************************************************************************
- (void) dealloc
{
[self stopObservation];
}
#pragma mark - Configuration
// *********************************************************************************************************************
// - observingOptions
// *********************************************************************************************************************
- (NSKeyValueObservingOptions) observingOptions
{
return NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
}
// *********************************************************************************************************************
// - observingContext
// *********************************************************************************************************************
- (void*) observingContext
{
return &_idNumber;
}
#pragma mark - Interface
// *********************************************************************************************************************
// - newValue
// *********************************************************************************************************************
- (id) newValue
{
return [self observationChange][NSKeyValueChangeNewKey];
}
// *********************************************************************************************************************
// - oldValue
// *********************************************************************************************************************
- (id) oldValue
{
return [self observationChange][NSKeyValueChangeOldKey];
}
// *********************************************************************************************************************
// - changeIndexes
// *********************************************************************************************************************
- (NSIndexSet*) changeIndexes
{
return [self observationChange][NSKeyValueChangeIndexesKey];
}
// *********************************************************************************************************************
// - changeType
// *********************************************************************************************************************
- (NSKeyValueChange) changeType
{
return [[self observationChange][NSKeyValueChangeKindKey] integerValue];
}
#pragma mark - Key Value Observing
// *********************************************************************************************************************
// - observeValueForKeyPath: ofObject: change: context:
// *********************************************************************************************************************
- (void) observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context
{
if (self.isSuspended) {
return;
}
if (context != [self observingContext]) {
DNLog(@"Observation for unknown context, skipping action");
return;
}
if (![self.target isEqual:object] || ![self.key isEqual:keyPath]) {
DNLog(@"Observation for unknown object or key path, skipping action");
return;
}
if (!self.observer) {
DNLog(@"Observer has been deallocated, skipping action");
return;
}
if (! [[change valueForKey:NSKeyValueChangeNewKey] isEqual:[change valueForKey:NSKeyValueChangeOldKey]]) {
if (self.actionBlock) {
self.observationChange = change;
self.actionBlock( self.observer, self );
}
else {
DNLog(@"WARNING: No action block specified when observing %@ key %@",object, keyPath);
}
}
}
// *********************************************************************************************************************
// - startObservation
// *********************************************************************************************************************
- (void) startObservation
{
if (!self.isObserving) {
[self.target addObserver:self forKeyPath:self.key options:[self observingOptions] context:[self observingContext]];
self.observing = YES;
}
}
// *********************************************************************************************************************
// - stopObservation
// *********************************************************************************************************************
- (void) stopObservation
{
if (self.isObserving) {
[self.target removeObserver:self forKeyPath:self.key context:[self observingContext]];
self.observing = NO;
}
}
// *********************************************************************************************************************
// - suspendObservation
// *********************************************************************************************************************
- (void) suspendObservation
{
self.suspended = YES;
}
// *********************************************************************************************************************
// - resumeObservation
// *********************************************************************************************************************
- (void) resumeObservation
{
self.suspended = NO;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment