NSObject+RACKVOWrapper.m 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. //
  2. // NSObject+RACKVOWrapper.m
  3. // ReactiveObjC
  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 <ReactiveObjC/RACEXTRuntimeExtensions.h>
  10. #import <ReactiveObjC/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. @implementation NSObject (RACKVOWrapper)
  18. - (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block {
  19. NSCParameterAssert(block != nil);
  20. NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0);
  21. keyPath = [keyPath copy];
  22. NSObject *strongObserver = weakObserver;
  23. NSArray *keyPathComponents = keyPath.rac_keyPathComponents;
  24. BOOL keyPathHasOneComponent = (keyPathComponents.count == 1);
  25. NSString *keyPathHead = keyPathComponents[0];
  26. NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent;
  27. RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
  28. // The disposable that groups all disposal necessary to clean up the callbacks
  29. // added to the value of the first key path component.
  30. RACSerialDisposable *firstComponentSerialDisposable = [RACSerialDisposable serialDisposableWithDisposable:[RACCompoundDisposable compoundDisposable]];
  31. RACCompoundDisposable * (^firstComponentDisposable)(void) = ^{
  32. return (RACCompoundDisposable *)firstComponentSerialDisposable.disposable;
  33. };
  34. [disposable addDisposable:firstComponentSerialDisposable];
  35. BOOL shouldAddDeallocObserver = NO;
  36. objc_property_t property = class_getProperty(object_getClass(self), keyPathHead.UTF8String);
  37. if (property != NULL) {
  38. rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property);
  39. if (attributes != NULL) {
  40. @onExit {
  41. free(attributes);
  42. };
  43. BOOL isObject = attributes->objectClass != nil || strstr(attributes->type, @encode(id)) == attributes->type;
  44. BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol");
  45. BOOL isBlock = strcmp(attributes->type, @encode(void(^)(void))) == 0;
  46. BOOL isWeak = attributes->weak;
  47. // If this property isn't actually an object (or is a Class object),
  48. // no point in observing the deallocation of the wrapper returned by
  49. // KVC.
  50. //
  51. // If this property is an object, but not declared `weak`, we
  52. // don't need to watch for it spontaneously being set to nil.
  53. //
  54. // Attempting to observe non-weak properties will result in
  55. // broken behavior for dynamic getters, so don't even try.
  56. shouldAddDeallocObserver = isObject && isWeak && !isBlock && !isProtocol;
  57. }
  58. }
  59. // Adds the callback block to the value's deallocation. Also adds the logic to
  60. // clean up the callback to the firstComponentDisposable.
  61. void (^addDeallocObserverToPropertyValue)(NSObject *) = ^(NSObject *value) {
  62. if (!shouldAddDeallocObserver) return;
  63. // If a key path value is the observer, commonly when a key path begins
  64. // with "self", we prevent deallocation triggered callbacks for any such key
  65. // path components. Thus, the observer's deallocation is not considered a
  66. // change to the key path.
  67. if (value == weakObserver) return;
  68. NSDictionary *change = @{
  69. NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting),
  70. NSKeyValueChangeNewKey: NSNull.null,
  71. };
  72. RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable;
  73. RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{
  74. block(nil, change, YES, keyPathHasOneComponent);
  75. }];
  76. [valueDisposable addDisposable:deallocDisposable];
  77. [firstComponentDisposable() addDisposable:[RACDisposable disposableWithBlock:^{
  78. [valueDisposable removeDisposable:deallocDisposable];
  79. }]];
  80. };
  81. // Adds the callback block to the remaining path components on the value. Also
  82. // adds the logic to clean up the callbacks to the firstComponentDisposable.
  83. void (^addObserverToValue)(NSObject *) = ^(NSObject *value) {
  84. RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:weakObserver block:block];
  85. [firstComponentDisposable() addDisposable:observerDisposable];
  86. };
  87. // Observe only the first key path component, when the value changes clean up
  88. // the callbacks on the old value, add callbacks to the new value and call the
  89. // callback block as needed.
  90. //
  91. // Note this does not use NSKeyValueObservingOptionInitial so this only
  92. // handles changes to the value, callbacks to the initial value must be added
  93. // separately.
  94. NSKeyValueObservingOptions trampolineOptions = (options | NSKeyValueObservingOptionPrior) & ~NSKeyValueObservingOptionInitial;
  95. RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:strongObserver keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) {
  96. // If this is a prior notification, clean up all the callbacks added to the
  97. // previous value and call the callback block. Everything else is deferred
  98. // until after we get the notification after the change.
  99. if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
  100. [firstComponentDisposable() dispose];
  101. if ((options & NSKeyValueObservingOptionPrior) != 0) {
  102. block([trampolineTarget valueForKeyPath:keyPath], change, NO, keyPathHasOneComponent);
  103. }
  104. return;
  105. }
  106. // From here the notification is not prior.
  107. NSObject *value = [trampolineTarget valueForKey:keyPathHead];
  108. // If the value has changed but is nil, there is no need to add callbacks to
  109. // it, just call the callback block.
  110. if (value == nil) {
  111. block(nil, change, NO, keyPathHasOneComponent);
  112. return;
  113. }
  114. // From here the notification is not prior and the value is not nil.
  115. // Create a new firstComponentDisposable while getting rid of the old one at
  116. // the same time, in case this is being called concurrently.
  117. RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]];
  118. [oldFirstComponentDisposable dispose];
  119. addDeallocObserverToPropertyValue(value);
  120. // If there are no further key path components, there is no need to add the
  121. // other callbacks, just call the callback block with the value itself.
  122. if (keyPathHasOneComponent) {
  123. block(value, change, NO, keyPathHasOneComponent);
  124. return;
  125. }
  126. // The value has changed, is not nil, and there are more key path components
  127. // to consider. Add the callbacks to the value for the remaining key path
  128. // components and call the callback block with the current value of the full
  129. // key path.
  130. addObserverToValue(value);
  131. block([value valueForKeyPath:keyPathTail], change, NO, keyPathHasOneComponent);
  132. }];
  133. // Stop the KVO observation when this one is disposed of.
  134. [disposable addDisposable:trampoline];
  135. // Add the callbacks to the initial value if needed.
  136. NSObject *value = [self valueForKey:keyPathHead];
  137. if (value != nil) {
  138. addDeallocObserverToPropertyValue(value);
  139. if (!keyPathHasOneComponent) {
  140. addObserverToValue(value);
  141. }
  142. }
  143. // Call the block with the initial value if needed.
  144. if ((options & NSKeyValueObservingOptionInitial) != 0) {
  145. id initialValue = [self valueForKeyPath:keyPath];
  146. NSDictionary *initialChange = @{
  147. NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting),
  148. NSKeyValueChangeNewKey: initialValue ?: NSNull.null,
  149. };
  150. block(initialValue, initialChange, NO, keyPathHasOneComponent);
  151. }
  152. RACCompoundDisposable *observerDisposable = strongObserver.rac_deallocDisposable;
  153. RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable;
  154. // Dispose of this observation if the receiver or the observer deallocate.
  155. [observerDisposable addDisposable:disposable];
  156. [selfDisposable addDisposable:disposable];
  157. return [RACDisposable disposableWithBlock:^{
  158. [disposable dispose];
  159. [observerDisposable removeDisposable:disposable];
  160. [selfDisposable removeDisposable:disposable];
  161. }];
  162. }
  163. @end