Last active
April 26, 2017 03:54
-
-
Save swillits/fce334146465f55978c4ba491153e0cf to your computer and use it in GitHub Desktop.
Obj-C Preferences Observation
This file contains hidden or 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
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 |
This file contains hidden or 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
#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 |
This file contains hidden or 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
@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