| // |
| // NSObject+RACAppKitBindings.m |
| // ReactiveCocoa |
| // |
| // Created by Josh Abernathy on 4/17/12. |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| // |
| |
| #import "NSObject+RACAppKitBindings.h" |
| #import "EXTKeyPathCoding.h" |
| #import "EXTScope.h" |
| #import "NSObject+RACDeallocating.h" |
| #import "RACChannel.h" |
| #import "RACCompoundDisposable.h" |
| #import "RACDisposable.h" |
| #import "RACKVOChannel.h" |
| #import "RACMulticastConnection.h" |
| #import "RACSignal+Operations.h" |
| #import "RACValueTransformer.h" |
| #import <objc/runtime.h> |
| |
| // Used as an object to bind to, so we can hide the object creation and just |
| // expose a RACChannel instead. |
| @interface RACChannelProxy : NSObject |
| |
| // The RACChannel used for this Cocoa binding. |
| @property (nonatomic, strong, readonly) RACChannel *channel; |
| |
| // The KVC- and KVO-compliant property to be read and written by the Cocoa |
| // binding. |
| // |
| // This should not be set manually. |
| @property (nonatomic, strong) id value; |
| |
| // The target of the Cocoa binding. |
| // |
| // This should be set to nil when the target deallocates. |
| @property (atomic, unsafe_unretained) id target; |
| |
| // The name of the Cocoa binding used. |
| @property (nonatomic, copy, readonly) NSString *bindingName; |
| |
| // Improves the performance of KVO on the receiver. |
| // |
| // See the documentation for <NSKeyValueObserving> for more information. |
| @property (atomic, assign) void *observationInfo; |
| |
| // Initializes the receiver and binds to the given target. |
| // |
| // target - The target of the Cocoa binding. This must not be nil. |
| // bindingName - The name of the Cocoa binding to use. This must not be nil. |
| // options - Any options to pass to the binding. This may be nil. |
| // |
| // Returns an initialized channel proxy. |
| - (id)initWithTarget:(id)target bindingName:(NSString *)bindingName options:(NSDictionary *)options; |
| |
| @end |
| |
| @implementation NSObject (RACAppKitBindings) |
| |
| - (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding { |
| return [self rac_channelToBinding:binding options:nil]; |
| } |
| |
| - (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding options:(NSDictionary *)options { |
| NSCParameterAssert(binding != nil); |
| |
| RACChannelProxy *proxy = [[RACChannelProxy alloc] initWithTarget:self bindingName:binding options:options]; |
| return proxy.channel.leadingTerminal; |
| } |
| |
| @end |
| |
| @implementation RACChannelProxy |
| |
| #pragma mark Properties |
| |
| - (void)setValue:(id)value { |
| [self willChangeValueForKey:@keypath(self.value)]; |
| _value = value; |
| [self didChangeValueForKey:@keypath(self.value)]; |
| } |
| |
| #pragma mark Lifecycle |
| |
| - (id)initWithTarget:(id)target bindingName:(NSString *)bindingName options:(NSDictionary *)options { |
| NSCParameterAssert(target != nil); |
| NSCParameterAssert(bindingName != nil); |
| |
| self = [super init]; |
| if (self == nil) return nil; |
| |
| _target = target; |
| _bindingName = [bindingName copy]; |
| _channel = [[RACChannel alloc] init]; |
| |
| @weakify(self); |
| |
| void (^cleanUp)() = ^{ |
| @strongify(self); |
| |
| id target = self.target; |
| if (target == nil) return; |
| |
| self.target = nil; |
| |
| [target unbind:bindingName]; |
| objc_setAssociatedObject(target, (__bridge void *)self, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| }; |
| |
| // When the channel terminates, tear down this proxy. |
| [self.channel.followingTerminal subscribeError:^(NSError *error) { |
| cleanUp(); |
| } completed:cleanUp]; |
| |
| [self.target bind:bindingName toObject:self withKeyPath:@keypath(self.value) options:options]; |
| |
| // Keep the proxy alive as long as the target, or until the property subject |
| // terminates. |
| objc_setAssociatedObject(self.target, (__bridge void *)self, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| |
| [[self.target rac_deallocDisposable] addDisposable:[RACDisposable disposableWithBlock:^{ |
| @strongify(self); |
| [self.channel.followingTerminal sendCompleted]; |
| }]]; |
| |
| RACChannelTo(self, value, options[NSNullPlaceholderBindingOption]) = self.channel.followingTerminal; |
| return self; |
| } |
| |
| - (void)dealloc { |
| [self.channel.followingTerminal sendCompleted]; |
| } |
| |
| #pragma mark NSObject |
| |
| - (NSString *)description { |
| return [NSString stringWithFormat:@"<%@: %p>{ target: %@, binding: %@ }", self.class, self, self.target, self.bindingName]; |
| } |
| |
| #pragma mark NSKeyValueObserving |
| |
| + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { |
| // Generating manual notifications for `value` is simpler and more |
| // performant than having KVO swizzle our class and add its own logic. |
| return NO; |
| } |
| |
| @end |
| |
| @implementation NSObject (RACAppKitBindingsDeprecated) |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| |
| - (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath { |
| [self rac_bind:binding toObject:object withKeyPath:keyPath nilValue:nil]; |
| } |
| |
| - (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath nilValue:(id)nilValue { |
| [self bind:binding toObject:object withKeyPath:keyPath options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSContinuouslyUpdatesValueBindingOption, nilValue, NSNullPlaceholderBindingOption, nil]]; |
| } |
| |
| - (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath transform:(id (^)(id value))transformBlock { |
| RACValueTransformer *transformer = [RACValueTransformer transformerWithBlock:transformBlock]; |
| [self bind:binding toObject:object withKeyPath:keyPath options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSContinuouslyUpdatesValueBindingOption, transformer, NSValueTransformerBindingOption, nil]]; |
| } |
| |
| - (void)rac_bind:(NSString *)binding toObject:(id)object withNegatedKeyPath:(NSString *)keyPath { |
| [self bind:binding toObject:object withKeyPath:keyPath options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSContinuouslyUpdatesValueBindingOption, NSNegateBooleanTransformerName, NSValueTransformerNameBindingOption, nil]]; |
| } |
| |
| #pragma clang diagnostic pop |
| |
| @end |