Created
August 31, 2016 23:29
-
-
Save dave-fn/fd306639b2c4e74cb4c9d4ed0e3bc606 to your computer and use it in GitHub Desktop.
Objective-C block based Key-Value Observing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ********************************************************************************************************************* | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ********************************************************************************************************************* | |
// 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