NSObject+RACAppKitBindings.m 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. //
  2. // NSObject+RACAppKitBindings.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Josh Abernathy on 4/17/12.
  6. // Copyright (c) 2012 GitHub, Inc. All rights reserved.
  7. //
  8. #import "NSObject+RACAppKitBindings.h"
  9. #import <ReactiveCocoa/EXTKeyPathCoding.h>
  10. #import <ReactiveCocoa/EXTScope.h>
  11. #import "NSObject+RACDeallocating.h"
  12. #import "RACChannel.h"
  13. #import "RACCompoundDisposable.h"
  14. #import "RACDisposable.h"
  15. #import "RACKVOChannel.h"
  16. #import "RACValueTransformer.h"
  17. #import <objc/runtime.h>
  18. // Used as an object to bind to, so we can hide the object creation and just
  19. // expose a RACChannel instead.
  20. @interface RACChannelProxy : NSObject
  21. // The RACChannel used for this Cocoa binding.
  22. @property (nonatomic, strong, readonly) RACChannel *channel;
  23. // The KVC- and KVO-compliant property to be read and written by the Cocoa
  24. // binding.
  25. //
  26. // This should not be set manually.
  27. @property (nonatomic, strong) id value;
  28. // The target of the Cocoa binding.
  29. //
  30. // This should be set to nil when the target deallocates.
  31. @property (atomic, unsafe_unretained) id target;
  32. // The name of the Cocoa binding used.
  33. @property (nonatomic, copy, readonly) NSString *bindingName;
  34. // Improves the performance of KVO on the receiver.
  35. //
  36. // See the documentation for <NSKeyValueObserving> for more information.
  37. @property (atomic, assign) void *observationInfo;
  38. // Initializes the receiver and binds to the given target.
  39. //
  40. // target - The target of the Cocoa binding. This must not be nil.
  41. // bindingName - The name of the Cocoa binding to use. This must not be nil.
  42. // options - Any options to pass to the binding. This may be nil.
  43. //
  44. // Returns an initialized channel proxy.
  45. - (id)initWithTarget:(id)target bindingName:(NSString *)bindingName options:(NSDictionary *)options;
  46. @end
  47. @implementation NSObject (RACAppKitBindings)
  48. - (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding {
  49. return [self rac_channelToBinding:binding options:nil];
  50. }
  51. - (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding options:(NSDictionary *)options {
  52. NSCParameterAssert(binding != nil);
  53. RACChannelProxy *proxy = [[RACChannelProxy alloc] initWithTarget:self bindingName:binding options:options];
  54. return proxy.channel.leadingTerminal;
  55. }
  56. @end
  57. @implementation RACChannelProxy
  58. #pragma mark Properties
  59. - (void)setValue:(id)value {
  60. [self willChangeValueForKey:@keypath(self.value)];
  61. _value = value;
  62. [self didChangeValueForKey:@keypath(self.value)];
  63. }
  64. #pragma mark Lifecycle
  65. - (id)initWithTarget:(id)target bindingName:(NSString *)bindingName options:(NSDictionary *)options {
  66. NSCParameterAssert(target != nil);
  67. NSCParameterAssert(bindingName != nil);
  68. self = [super init];
  69. if (self == nil) return nil;
  70. _target = target;
  71. _bindingName = [bindingName copy];
  72. _channel = [[RACChannel alloc] init];
  73. @weakify(self);
  74. void (^cleanUp)() = ^{
  75. @strongify(self);
  76. id target = self.target;
  77. if (target == nil) return;
  78. self.target = nil;
  79. [target unbind:bindingName];
  80. objc_setAssociatedObject(target, (__bridge void *)self, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  81. };
  82. // When the channel terminates, tear down this proxy.
  83. [self.channel.followingTerminal subscribeError:^(NSError *error) {
  84. cleanUp();
  85. } completed:cleanUp];
  86. [self.target bind:bindingName toObject:self withKeyPath:@keypath(self.value) options:options];
  87. // Keep the proxy alive as long as the target, or until the property subject
  88. // terminates.
  89. objc_setAssociatedObject(self.target, (__bridge void *)self, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  90. [[self.target rac_deallocDisposable] addDisposable:[RACDisposable disposableWithBlock:^{
  91. @strongify(self);
  92. [self.channel.followingTerminal sendCompleted];
  93. }]];
  94. RACChannelTo(self, value, options[NSNullPlaceholderBindingOption]) = self.channel.followingTerminal;
  95. return self;
  96. }
  97. - (void)dealloc {
  98. [self.channel.followingTerminal sendCompleted];
  99. }
  100. #pragma mark NSObject
  101. - (NSString *)description {
  102. return [NSString stringWithFormat:@"<%@: %p>{ target: %@, binding: %@ }", self.class, self, self.target, self.bindingName];
  103. }
  104. #pragma mark NSKeyValueObserving
  105. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
  106. // Generating manual notifications for `value` is simpler and more
  107. // performant than having KVO swizzle our class and add its own logic.
  108. return NO;
  109. }
  110. @end