| // |
| // NSObject+RACPropertySubscribing.m |
| // ReactiveCocoa |
| // |
| // Created by Josh Abernathy on 3/2/12. |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| // |
| |
| #import "NSObject+RACPropertySubscribing.h" |
| #import "RACEXTScope.h" |
| #import "NSObject+RACDeallocating.h" |
| #import "NSObject+RACDescription.h" |
| #import "NSObject+RACKVOWrapper.h" |
| #import "RACCompoundDisposable.h" |
| #import "RACDisposable.h" |
| #import "RACKVOTrampoline.h" |
| #import "RACSubscriber.h" |
| #import "RACSignal+Operations.h" |
| #import "RACTuple.h" |
| #import <libkern/OSAtomic.h> |
| |
| @implementation NSObject (RACPropertySubscribing) |
| |
| - (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| return [[[self |
| rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] |
| reduceEach:^(id value, NSDictionary *change) { |
| return value; |
| }] |
| setNameWithFormat:@"RACObserve(%@, %@)", self.rac_description, keyPath]; |
| } |
| |
| - (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer { |
| keyPath = [keyPath copy]; |
| |
| NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init]; |
| objectLock.name = @"com.github.ReactiveCocoa.NSObjectRACPropertySubscribing"; |
| |
| __block __unsafe_unretained NSObject *unsafeSelf = self; |
| __block __unsafe_unretained NSObject *unsafeObserver = observer; |
| |
| RACSignal *deallocSignal = [[RACSignal |
| zip:@[ |
| self.rac_willDeallocSignal, |
| observer.rac_willDeallocSignal ?: [RACSignal never] |
| ]] |
| doCompleted:^{ |
| // Forces deallocation to wait if the object variables are currently |
| // being read on another thread. |
| [objectLock lock]; |
| @onExit { |
| [objectLock unlock]; |
| }; |
| |
| unsafeSelf = nil; |
| unsafeObserver = nil; |
| }]; |
| |
| return [[[RACSignal |
| createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| // Hold onto the lock the whole time we're setting up the KVO |
| // observation, because any resurrection that might be caused by our |
| // retaining below must be balanced out by the time -dealloc returns |
| // (if another thread is waiting on the lock above). |
| [objectLock lock]; |
| @onExit { |
| [objectLock unlock]; |
| }; |
| |
| __strong NSObject *observer __attribute__((objc_precise_lifetime)) = unsafeObserver; |
| __strong NSObject *self __attribute__((objc_precise_lifetime)) = unsafeSelf; |
| |
| if (self == nil) { |
| [subscriber sendCompleted]; |
| return nil; |
| } |
| |
| return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change) { |
| [subscriber sendNext:RACTuplePack(value, change)]; |
| }]; |
| }] |
| takeUntil:deallocSignal] |
| setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", self.rac_description, keyPath, (unsigned long)options, observer.rac_description]; |
| } |
| |
| @end |
| |
| static RACSignal *signalWithoutChangesFor(Class class, NSObject *object, NSString *keyPath, NSKeyValueObservingOptions options, NSObject *observer) { |
| NSCParameterAssert(object != nil); |
| NSCParameterAssert(keyPath != nil); |
| NSCParameterAssert(observer != nil); |
| |
| keyPath = [keyPath copy]; |
| |
| @unsafeify(object); |
| |
| return [[class |
| rac_signalWithChangesFor:object keyPath:keyPath options:options observer:observer] |
| map:^(NSDictionary *change) { |
| @strongify(object); |
| return [object valueForKeyPath:keyPath]; |
| }]; |
| } |
| |
| @implementation NSObject (RACPropertySubscribingDeprecated) |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| |
| + (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| return signalWithoutChangesFor(self, object, keyPath, 0, observer); |
| } |
| |
| + (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| return signalWithoutChangesFor(self, object, keyPath, NSKeyValueObservingOptionInitial, observer); |
| } |
| |
| + (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer { |
| @unsafeify(observer, object); |
| return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| |
| @strongify(observer, object); |
| RACKVOTrampoline *KVOTrampoline = [object rac_addObserver:observer forKeyPath:keyPath options:options block:^(id target, id observer, NSDictionary *change) { |
| [subscriber sendNext:change]; |
| }]; |
| |
| @weakify(subscriber); |
| RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ |
| @strongify(subscriber); |
| [KVOTrampoline dispose]; |
| [subscriber sendCompleted]; |
| }]; |
| |
| [observer.rac_deallocDisposable addDisposable:deallocDisposable]; |
| [object.rac_deallocDisposable addDisposable:deallocDisposable]; |
| |
| RACCompoundDisposable *observerDisposable = observer.rac_deallocDisposable; |
| RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable; |
| return [RACDisposable disposableWithBlock:^{ |
| [observerDisposable removeDisposable:deallocDisposable]; |
| [objectDisposable removeDisposable:deallocDisposable]; |
| [KVOTrampoline dispose]; |
| }]; |
| }] setNameWithFormat:@"RACAble(%@, %@)", object.rac_description, keyPath]; |
| } |
| |
| - (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| return [self.class rac_signalFor:self keyPath:keyPath observer:observer]; |
| } |
| |
| - (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| return [self.class rac_signalWithStartingValueFor:self keyPath:keyPath observer:observer]; |
| } |
| |
| - (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal { |
| return [signal setKeyPath:keyPath onObject:self]; |
| } |
| |
| #pragma clang diagnostic pop |
| |
| @end |