NSObject+RACKVOWrapper.m 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. //
  2. // NSObject+RACKVOWrapper.m
  3. // GitHub
  4. //
  5. // Created by Josh Abernathy on 10/11/11.
  6. // Copyright (c) 2011 GitHub. All rights reserved.
  7. //
  8. #import "NSObject+RACKVOWrapper.h"
  9. #import "RACEXTRuntimeExtensions.h"
  10. #import "RACEXTScope.h"
  11. #import "NSObject+RACDeallocating.h"
  12. #import "NSString+RACKeyPathUtilities.h"
  13. #import "RACCompoundDisposable.h"
  14. #import "RACDisposable.h"
  15. #import "RACKVOTrampoline.h"
  16. #import "RACSerialDisposable.h"
  17. NSString * const RACKeyValueChangeCausedByDeallocationKey = @"RACKeyValueChangeCausedByDeallocationKey";
  18. NSString * const RACKeyValueChangeAffectedOnlyLastComponentKey = @"RACKeyValueChangeAffectedOnlyLastComponentKey";
  19. @implementation NSObject (RACKVOWrapper)
  20. - (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer block:(void (^)(id, NSDictionary *))block {
  21. NSCParameterAssert(block != nil);
  22. NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0);
  23. keyPath = [keyPath copy];
  24. @unsafeify(observer);
  25. NSArray *keyPathComponents = keyPath.rac_keyPathComponents;
  26. BOOL keyPathHasOneComponent = (keyPathComponents.count == 1);
  27. NSString *keyPathHead = keyPathComponents[0];
  28. NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent;
  29. RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
  30. // The disposable that groups all disposal necessary to clean up the callbacks
  31. // added to the value of the first key path component.
  32. RACSerialDisposable *firstComponentSerialDisposable = [RACSerialDisposable serialDisposableWithDisposable:[RACCompoundDisposable compoundDisposable]];
  33. RACCompoundDisposable * (^firstComponentDisposable)(void) = ^{
  34. return (RACCompoundDisposable *)firstComponentSerialDisposable.disposable;
  35. };
  36. [disposable addDisposable:firstComponentSerialDisposable];
  37. // Adds the callback block to the value's deallocation. Also adds the logic to
  38. // clean up the callback to the firstComponentDisposable.
  39. void (^addDeallocObserverToPropertyValue)(NSObject *, NSString *, NSObject *) = ^(NSObject *parent, NSString *propertyKey, NSObject *value) {
  40. // If a key path value is the observer, commonly when a key path begins
  41. // with "self", we prevent deallocation triggered callbacks for any such key
  42. // path components. Thus, the observer's deallocation is not considered a
  43. // change to the key path.
  44. @strongify(observer);
  45. if (value == observer) return;
  46. objc_property_t property = class_getProperty(object_getClass(parent), propertyKey.UTF8String);
  47. if (property == NULL) {
  48. // If we can't find an Objective-C property for this key, we assume
  49. // that we don't need to observe its deallocation (thus matching
  50. // vanilla KVO behavior).
  51. //
  52. // Even if we wanted to, there's not enough type information on
  53. // ivars to figure out its memory management.
  54. return;
  55. }
  56. rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property);
  57. if (attributes == NULL) return;
  58. @onExit {
  59. free(attributes);
  60. };
  61. if (attributes->objectClass == nil && strcmp(attributes->type, @encode(id)) != 0) {
  62. // If this property isn't actually an object (or is a Class object),
  63. // no point in observing the deallocation of the wrapper returned by
  64. // KVC.
  65. return;
  66. }
  67. if (!attributes->weak) {
  68. // If this property is an object, but not declared `weak`, we
  69. // don't need to watch for it spontaneously being set to nil.
  70. //
  71. // Attempting to observe non-weak properties will result in
  72. // broken behavior for dynamic getters, so don't even try.
  73. return;
  74. }
  75. NSDictionary *change = @{
  76. NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting),
  77. NSKeyValueChangeNewKey: NSNull.null,
  78. RACKeyValueChangeCausedByDeallocationKey: @YES,
  79. RACKeyValueChangeAffectedOnlyLastComponentKey: @(keyPathHasOneComponent)
  80. };
  81. RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable;
  82. RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{
  83. block(nil, change);
  84. }];
  85. [valueDisposable addDisposable:deallocDisposable];
  86. [firstComponentDisposable() addDisposable:[RACDisposable disposableWithBlock:^{
  87. [valueDisposable removeDisposable:deallocDisposable];
  88. }]];
  89. };
  90. // Adds the callback block to the remaining path components on the value. Also
  91. // adds the logic to clean up the callbacks to the firstComponentDisposable.
  92. void (^addObserverToValue)(NSObject *) = ^(NSObject *value) {
  93. @strongify(observer);
  94. RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:observer block:block];
  95. [firstComponentDisposable() addDisposable:observerDisposable];
  96. };
  97. // Observe only the first key path component, when the value changes clean up
  98. // the callbacks on the old value, add callbacks to the new value and call the
  99. // callback block as needed.
  100. //
  101. // Note this does not use NSKeyValueObservingOptionInitial so this only
  102. // handles changes to the value, callbacks to the initial value must be added
  103. // separately.
  104. NSKeyValueObservingOptions trampolineOptions = (options | NSKeyValueObservingOptionPrior) & ~NSKeyValueObservingOptionInitial;
  105. RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:observer keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) {
  106. // Prepare the change dictionary by adding the RAC specific keys
  107. {
  108. NSMutableDictionary *newChange = [change mutableCopy];
  109. newChange[RACKeyValueChangeCausedByDeallocationKey] = @NO;
  110. newChange[RACKeyValueChangeAffectedOnlyLastComponentKey] = @(keyPathHasOneComponent);
  111. change = newChange.copy;
  112. }
  113. // If this is a prior notification, clean up all the callbacks added to the
  114. // previous value and call the callback block. Everything else is deferred
  115. // until after we get the notification after the change.
  116. if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
  117. [firstComponentDisposable() dispose];
  118. if ((options & NSKeyValueObservingOptionPrior) != 0) {
  119. block([trampolineTarget valueForKeyPath:keyPath], change);
  120. }
  121. return;
  122. }
  123. // From here the notification is not prior.
  124. NSObject *value = [trampolineTarget valueForKey:keyPathHead];
  125. // If the value has changed but is nil, there is no need to add callbacks to
  126. // it, just call the callback block.
  127. if (value == nil) {
  128. block(nil, change);
  129. return;
  130. }
  131. // From here the notification is not prior and the value is not nil.
  132. // Create a new firstComponentDisposable while getting rid of the old one at
  133. // the same time, in case this is being called concurrently.
  134. RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]];
  135. [oldFirstComponentDisposable dispose];
  136. addDeallocObserverToPropertyValue(trampolineTarget, keyPathHead, value);
  137. // If there are no further key path components, there is no need to add the
  138. // other callbacks, just call the callback block with the value itself.
  139. if (keyPathHasOneComponent) {
  140. block(value, change);
  141. return;
  142. }
  143. // The value has changed, is not nil, and there are more key path components
  144. // to consider. Add the callbacks to the value for the remaining key path
  145. // components and call the callback block with the current value of the full
  146. // key path.
  147. addObserverToValue(value);
  148. block([value valueForKeyPath:keyPathTail], change);
  149. }];
  150. // Stop the KVO observation when this one is disposed of.
  151. [disposable addDisposable:trampoline];
  152. // Add the callbacks to the initial value if needed.
  153. NSObject *value = [self valueForKey:keyPathHead];
  154. if (value != nil) {
  155. addDeallocObserverToPropertyValue(self, keyPathHead, value);
  156. if (!keyPathHasOneComponent) {
  157. addObserverToValue(value);
  158. }
  159. }
  160. // Call the block with the initial value if needed.
  161. if ((options & NSKeyValueObservingOptionInitial) != 0) {
  162. id initialValue = [self valueForKeyPath:keyPath];
  163. NSDictionary *initialChange = @{
  164. NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting),
  165. NSKeyValueChangeNewKey: initialValue ?: NSNull.null,
  166. RACKeyValueChangeCausedByDeallocationKey: @NO,
  167. RACKeyValueChangeAffectedOnlyLastComponentKey: @(keyPathHasOneComponent)
  168. };
  169. block(initialValue, initialChange);
  170. }
  171. RACCompoundDisposable *observerDisposable = observer.rac_deallocDisposable;
  172. RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable;
  173. // Dispose of this observation if the receiver or the observer deallocate.
  174. [observerDisposable addDisposable:disposable];
  175. [selfDisposable addDisposable:disposable];
  176. return disposable;
  177. }
  178. @end
  179. @implementation NSObject (RACKVOWrapperDeprecated)
  180. #pragma clang diagnostic push
  181. #pragma clang diagnostic ignored "-Wdeprecated-implementations"
  182. - (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block {
  183. return [[RACKVOTrampoline alloc] initWithTarget:self observer:observer keyPath:keyPath options:options block:block];
  184. }
  185. #pragma clang diagnostic pop
  186. @end