Skip to content

Instantly share code, notes, and snippets.

@swillits
Last active April 26, 2017 03:54
Show Gist options
  • Save swillits/fce334146465f55978c4ba491153e0cf to your computer and use it in GitHub Desktop.
Save swillits/fce334146465f55978c4ba491153e0cf to your computer and use it in GitHub Desktop.
Obj-C Preferences Observation
typedef void (^KVOReceptionistBlock)(NSString *keyPath, id object, NSDictionary *change);
@interface AGKVOReceptionist : NSObject
{
id _observedObject;
NSString * _observedKeyPath;
KVOReceptionistBlock _handler;
BOOL _cancelled;
}
+ (instancetype)receptionistForKeyPath:(NSString *)keyPath object:(id)obj handler:(KVOReceptionistBlock)block;
- (void)cancel;
@property (nonatomic, readonly, getter=isCancelled) BOOL cancelled;
@end
#import "AGKVOReceptionist.h"
@implementation AGKVOReceptionist
+ (instancetype)receptionistForKeyPath:(NSString *)keyPath object:(id)obj handler:(KVOReceptionistBlock)handler;
{
AGKVOReceptionist * rc = [[[AGKVOReceptionist alloc] init] autorelease];
rc->_handler = [handler copy];
rc->_observedKeyPath = [keyPath copy];
rc->_observedObject = [obj retain];
[obj addObserver:rc forKeyPath:keyPath options:0 context:[AGKVOReceptionist class]];
return rc;
}
- (void)dealloc;
{
[self cancel];
[super dealloc];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == [AGKVOReceptionist class]) {
if (object == _observedObject && [keyPath isEqual:_observedKeyPath]) {
_handler(keyPath, object, change);
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)cancel;
{
if (_observedObject) {
[_observedObject removeObserver:self forKeyPath:_observedKeyPath context:[AGKVOReceptionist class]];
[_observedObject release];
[_observedKeyPath release];
[_handler release];
_observedObject = nil;
_observedKeyPath = nil;
_handler = nil;
_cancelled = YES;
}
}
- (BOOL)isCancelled;
{
return _cancelled;
}
@end
@interface AQPreferencesObservationInfo : NSObject <NSCopying>
@property (nonatomic, readwrite, assign) void * observer;
@property (nonatomic, readwrite, copy) NSString * prefKey;
@end
@implementation AQPreferences
{
NSMutableDictionary * observations;
}
+ (instancetype)sharedInstance
{
static AQPreferences * shared = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [[AQPreferences alloc] init];
shared->observations = [[NSMutableDictionary alloc] init];
});
return shared;
}
+ (void)addObserver:(id)observer forKey:(NSString *)prefKey handler:(void (^)(void))handler
{
[AQPreferences.sharedInstance addObserver:observer forPreferenceKey:prefKey handler:handler];
}
+ (void)removeObserver:(id)observer forKey:(NSString *)prefKey
{
[AQPreferences.sharedInstance removeObserver:observer forPreferenceKey:prefKey];
}
- (void)addObserver:(id)observer forPreferenceKey:(NSString *)prefKey handler:(void (^)(void))handler
{
AQPreferencesObservationInfo * info = [[[AQPreferencesObservationInfo alloc] init] autorelease];
info.observer = observer;
info.prefKey = prefKey;
@synchronized (self) {
AGWeak * weakObserver = [AGWeak ref:observer];
AGKVOReceptionist * receptionist = [AGKVOReceptionist receptionistForKeyPath:NSSTRF(@"values.%@", prefKey) object:NSUserDefaultsController.sharedUserDefaultsController handler:^(NSString *keyPath, id object, NSDictionary *change) {
id strongObserver = [weakObserver.ref retain];
if (strongObserver) {
handler();
[strongObserver release];
} else {
@synchronized (self) {
AGKVOReceptionist * r = [observations objectForKey:info];
[r cancel];
[observations removeObjectForKey:info];
}
}
}];
[observations setObject:receptionist forKey:info];
}
}
- (void)removeObserver:(id)observer forPreferenceKey:(NSString *)prefKey
{
AQPreferencesObservationInfo * info = [[[AQPreferencesObservationInfo alloc] init] autorelease];
info.observer = observer;
info.prefKey = prefKey;
@synchronized (self) {
AGKVOReceptionist * r = [observations objectForKey:info];
[r cancel];
[observations removeObjectForKey:info];
}
}
@end
@implementation AQPreferencesObservationInfo
- (void)dealloc
{
[_prefKey release];
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
return [self retain];
}
- (BOOL)isEqual:(id)object
{
if ([object isKindOfClass:[AQPreferencesObservationInfo class]]) return NO;
AQPreferencesObservationInfo * other = object;
return _observer == other->_observer && [_prefKey isEqual:other.prefKey];
}
- (NSUInteger)hash
{
return (NSUInteger)_observer ^ _prefKey.hash;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment