RACKVOChannel.m 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. //
  2. // RACKVOChannel.m
  3. // ReactiveObjC
  4. //
  5. // Created by Uri Baghin on 27/12/2012.
  6. // Copyright (c) 2012 GitHub, Inc. All rights reserved.
  7. //
  8. #import "RACKVOChannel.h"
  9. #import <ReactiveObjC/RACEXTScope.h>
  10. #import "NSObject+RACDeallocating.h"
  11. #import "NSObject+RACKVOWrapper.h"
  12. #import "NSString+RACKeyPathUtilities.h"
  13. #import "RACChannel.h"
  14. #import "RACCompoundDisposable.h"
  15. #import "RACDisposable.h"
  16. #import "RACSignal+Operations.h"
  17. // Key for the array of RACKVOChannel's additional thread local
  18. // data in the thread dictionary.
  19. static NSString * const RACKVOChannelDataDictionaryKey = @"RACKVOChannelKey";
  20. // Wrapper class for additional thread local data.
  21. @interface RACKVOChannelData : NSObject
  22. // The flag used to ignore updates the channel itself has triggered.
  23. @property (nonatomic, assign) BOOL ignoreNextUpdate;
  24. // A pointer to the owner of the data. Only use this for pointer comparison,
  25. // never as an object reference.
  26. @property (nonatomic, assign) void *owner;
  27. + (instancetype)dataForChannel:(RACKVOChannel *)channel;
  28. @end
  29. @interface RACKVOChannel ()
  30. // The object whose key path the channel is wrapping.
  31. @property (atomic, weak) NSObject *target;
  32. // The key path the channel is wrapping.
  33. @property (nonatomic, copy, readonly) NSString *keyPath;
  34. // Returns the existing thread local data container or nil if none exists.
  35. @property (nonatomic, strong, readonly) RACKVOChannelData *currentThreadData;
  36. // Creates the thread local data container for the channel.
  37. - (void)createCurrentThreadData;
  38. // Destroy the thread local data container for the channel.
  39. - (void)destroyCurrentThreadData;
  40. @end
  41. @implementation RACKVOChannel
  42. #pragma mark Properties
  43. - (RACKVOChannelData *)currentThreadData {
  44. NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey];
  45. for (RACKVOChannelData *data in dataArray) {
  46. if (data.owner == (__bridge void *)self) return data;
  47. }
  48. return nil;
  49. }
  50. #pragma mark Lifecycle
  51. - (instancetype)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue {
  52. NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0);
  53. NSObject *strongTarget = target;
  54. self = [super init];
  55. _target = target;
  56. _keyPath = [keyPath copy];
  57. [self.leadingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -leadingTerminal", target, keyPath, nilValue];
  58. [self.followingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -followingTerminal", target, keyPath, nilValue];
  59. if (strongTarget == nil) {
  60. [self.leadingTerminal sendCompleted];
  61. return self;
  62. }
  63. // Observe the key path on target for changes and forward the changes to the
  64. // terminal.
  65. //
  66. // Intentionally capturing `self` strongly in the blocks below, so the
  67. // channel object stays alive while observing.
  68. RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  69. // If the change wasn't triggered by deallocation, only affects the last
  70. // path component, and ignoreNextUpdate is set, then it was triggered by
  71. // this channel and should not be forwarded.
  72. if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) {
  73. [self destroyCurrentThreadData];
  74. return;
  75. }
  76. [self.leadingTerminal sendNext:value];
  77. }];
  78. NSString *keyPathByDeletingLastKeyPathComponent = keyPath.rac_keyPathByDeletingLastKeyPathComponent;
  79. NSArray *keyPathComponents = keyPath.rac_keyPathComponents;
  80. NSUInteger keyPathComponentsCount = keyPathComponents.count;
  81. NSString *lastKeyPathComponent = keyPathComponents.lastObject;
  82. // Update the value of the property with the values received.
  83. [[self.leadingTerminal
  84. finally:^{
  85. [observationDisposable dispose];
  86. }]
  87. subscribeNext:^(id x) {
  88. // Check the value of the second to last key path component. Since the
  89. // channel can only update the value of a property on an object, and not
  90. // update intermediate objects, it can only update the value of the whole
  91. // key path if this object is not nil.
  92. NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target);
  93. if (object == nil) return;
  94. // Set the ignoreNextUpdate flag before setting the value so this channel
  95. // ignores the value in the subsequent -didChangeValueForKey: callback.
  96. [self createCurrentThreadData];
  97. self.currentThreadData.ignoreNextUpdate = YES;
  98. [object setValue:x ?: nilValue forKey:lastKeyPathComponent];
  99. } error:^(NSError *error) {
  100. NSCAssert(NO, @"Received error in %@: %@", self, error);
  101. // Log the error if we're running with assertions disabled.
  102. NSLog(@"Received error in %@: %@", self, error);
  103. }];
  104. // Capture `self` weakly for the target's deallocation disposable, so we can
  105. // freely deallocate if we complete before then.
  106. @weakify(self);
  107. [strongTarget.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  108. @strongify(self);
  109. [self.leadingTerminal sendCompleted];
  110. self.target = nil;
  111. }]];
  112. return self;
  113. }
  114. - (void)createCurrentThreadData {
  115. NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey];
  116. if (dataArray == nil) {
  117. dataArray = [NSMutableArray array];
  118. NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey] = dataArray;
  119. [dataArray addObject:[RACKVOChannelData dataForChannel:self]];
  120. return;
  121. }
  122. for (RACKVOChannelData *data in dataArray) {
  123. if (data.owner == (__bridge void *)self) return;
  124. }
  125. [dataArray addObject:[RACKVOChannelData dataForChannel:self]];
  126. }
  127. - (void)destroyCurrentThreadData {
  128. NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey];
  129. NSUInteger index = [dataArray indexOfObjectPassingTest:^ BOOL (RACKVOChannelData *data, NSUInteger idx, BOOL *stop) {
  130. return data.owner == (__bridge void *)self;
  131. }];
  132. if (index != NSNotFound) [dataArray removeObjectAtIndex:index];
  133. }
  134. @end
  135. @implementation RACKVOChannel (RACChannelTo)
  136. - (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key {
  137. NSCParameterAssert(key != nil);
  138. RACChannelTerminal *terminal = [self valueForKey:key];
  139. NSCAssert([terminal isKindOfClass:RACChannelTerminal.class], @"Key \"%@\" does not identify a channel terminal", key);
  140. return terminal;
  141. }
  142. - (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key {
  143. NSCParameterAssert(otherTerminal != nil);
  144. RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key];
  145. [otherTerminal subscribe:selfTerminal];
  146. [[selfTerminal skip:1] subscribe:otherTerminal];
  147. }
  148. @end
  149. @implementation RACKVOChannelData
  150. + (instancetype)dataForChannel:(RACKVOChannel *)channel {
  151. RACKVOChannelData *data = [[self alloc] init];
  152. data->_owner = (__bridge void *)channel;
  153. return data;
  154. }
  155. @end