/BlocksKit/NSObject+BlockObservation.m

http://github.com/zwaldowski/BlocksKit · Objective C · 147 lines · 106 code · 37 blank · 4 comment · 7 complexity · a3de8d1c03c2037c5d596309b0d8571a MD5 · raw file

  1. //
  2. // NSObject+BlockObservation.m
  3. // BlocksKit
  4. //
  5. #import "NSObject+BlockObservation.h"
  6. #import "NSObject+AssociatedObjects.h"
  7. #import "NSDictionary+BlocksKit.h"
  8. @interface BKObserver : NSObject
  9. @property (nonatomic, assign) id observee;
  10. @property (nonatomic, copy) NSString *keyPath;
  11. @property (nonatomic, copy) BKObservationBlock task;
  12. + (BKObserver *)observerForObject:(id)observee keyPath:(NSString *)keyPath task:(BKObservationBlock)task;
  13. @end
  14. static char kObserverBlocksKey;
  15. static char kBlockObservationContext;
  16. @implementation BKObserver
  17. @synthesize observee, keyPath, task;
  18. + (BKObserver *)observerForObject:(id)observee keyPath:(NSString *)newKeyPath task:(BKObservationBlock)newTask {
  19. BKObserver *instance = [BKObserver new];
  20. instance.observee = observee;
  21. instance.keyPath = newKeyPath;
  22. instance.task = newTask;
  23. return [instance autorelease];
  24. }
  25. - (void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  26. if (context == &kBlockObservationContext)
  27. self.task(object, change);
  28. }
  29. - (void)dealloc {
  30. self.task = nil;
  31. self.keyPath = nil;
  32. [super dealloc];
  33. }
  34. @end
  35. static dispatch_queue_t BKObserverMutationQueue() {
  36. static dispatch_queue_t queue = nil;
  37. static dispatch_once_t token = 0;
  38. dispatch_once(&token, ^{
  39. queue = dispatch_queue_create("org.blockskit.observers.queue", 0);
  40. });
  41. return queue;
  42. }
  43. @implementation NSObject (BlockObservation)
  44. - (NSString *)addObserverForKeyPath:(NSString *)keyPath task:(BKObservationBlock)task {
  45. NSString *token = [[NSProcessInfo processInfo] globallyUniqueString];
  46. [self addObserverForKeyPath:keyPath identifier:token task:task];
  47. return token;
  48. }
  49. - (void)addObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)identifier task:(BKObservationBlock)task {
  50. NSParameterAssert(keyPath);
  51. NSParameterAssert(identifier);
  52. NSParameterAssert(task);
  53. __block BKObserver *newObserver = nil;
  54. dispatch_sync(BKObserverMutationQueue(), ^{
  55. newObserver = [BKObserver observerForObject:self keyPath:keyPath task:task];
  56. NSMutableDictionary *dict = [self associatedValueForKey:&kObserverBlocksKey];
  57. if (!dict) {
  58. dict = [NSMutableDictionary dictionary];
  59. [self associateValue:dict withKey:&kObserverBlocksKey];
  60. }
  61. [dict setObject:newObserver forKey:[NSString stringWithFormat:@"%@_%@", keyPath, identifier]];
  62. });
  63. [self addObserver:newObserver forKeyPath:keyPath options:0 context:&kBlockObservationContext];
  64. }
  65. - (NSString *)addObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options task:(BKObservationBlock)task {
  66. NSString *token = [[NSProcessInfo processInfo] globallyUniqueString];
  67. [self addObserverForKeyPath:keyPath identifier:token options:options task:task];
  68. return token;
  69. }
  70. - (void)addObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options task:(BKObservationBlock)task {
  71. NSParameterAssert(keyPath);
  72. NSParameterAssert(identifier);
  73. NSParameterAssert(task);
  74. __block BKObserver *newObserver = nil;
  75. dispatch_sync(BKObserverMutationQueue(), ^{
  76. newObserver = [BKObserver observerForObject:self keyPath:keyPath task:task];
  77. NSMutableDictionary *dict = [self associatedValueForKey:&kObserverBlocksKey];
  78. if (!dict) {
  79. dict = [NSMutableDictionary dictionary];
  80. [self associateValue:dict withKey:&kObserverBlocksKey];
  81. }
  82. [dict setObject:newObserver forKey:[NSString stringWithFormat:@"%@_%@", keyPath, identifier]];
  83. });
  84. [self addObserver:newObserver forKeyPath:keyPath options:options context:&kBlockObservationContext];
  85. }
  86. - (void)removeObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)identifier {
  87. NSParameterAssert(keyPath);
  88. NSParameterAssert(identifier);
  89. dispatch_sync(BKObserverMutationQueue(), ^{
  90. NSString *token = [NSString stringWithFormat:@"%@_%@", keyPath, identifier];
  91. NSMutableDictionary *dict = [self associatedValueForKey:&kObserverBlocksKey];
  92. BKObserver *trampoline = [dict objectForKey:token];
  93. if (!trampoline || ![trampoline.keyPath isEqualToString:keyPath])
  94. return;
  95. [self removeObserver:trampoline forKeyPath:keyPath];
  96. [dict removeObjectForKey:token];
  97. if (!dict.count)
  98. [self associateValue:nil withKey:&kObserverBlocksKey];
  99. });
  100. }
  101. - (void)removeAllBlockObservers {
  102. dispatch_sync(BKObserverMutationQueue(), ^{
  103. NSMutableDictionary *observationDictionary = [self associatedValueForKey:&kObserverBlocksKey];
  104. [observationDictionary each:^(id key, id trampoline) {
  105. [self removeObserver:trampoline forKeyPath:[trampoline keyPath]];
  106. }];
  107. [self associateValue:nil withKey:&kObserverBlocksKey];
  108. });
  109. }
  110. @end