ReactiveCocoa 代码阅读笔记 (1) 双向绑定
我们首先从一个双向绑定的例子开始。假设我们的
里有一个1
UIViewController
和一个1
UITextField
, 我们希望在1
UILabel
输入时,
1
UITextField
里面同步显示一样的内容。我们通过把1
UILabel
和1
UITextField
绑定到同一个1
UILabel
,也就是1
model
的1
UIViewController
属性上
来实现这一点。1
name
RAC(self.nameTextfield, text) = RACObserve(self, name);
RAC(self.nameLabel, text) = RACObserve(self, name);
[self.nameTextfield.rac_textSignal subscribeNext:^(id x) {
self.name = x;
}];
// this has the same effect as the above
// RAC(self, name) = self.nameTextfield.rac_textSignal;
当
变化时,1
model
和1
self.nameTextfield
都会随之变化。而当1
self.nameLabel.text
中的输入发生变化时,1
UITextField
也会发生变化。 1
model
和1
UITextField
发生了双向绑定,而1
model
和1
label
是单向绑定。1
model
我们把上面的代码用
展开,看看到底发生了什么:1
clang -E
[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self.nameTextfield) nilValue:(((void *)0))][@(((void)(__objc_no && ((void)self.nameTextfield.text, __objc_no)), "text"))] = ({ __attribute__((objc_ownership(weak))) id target_ = (self); [target_ rac_valuesForKeyPath:@(((void)(__objc_no && ((void)self.name, __objc_no)), "name")) observer:self]; });
简化一下:
[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self.nameTextfield) nilValue:0][@"text"] = ({ __weak id target_ = (self); [target_ rac_valuesForKeyPath:@("name") observer:self]; });
等等,
这个是什么东西? 这个东西其实就等效于1
@(((void)(__objc_no && ((void)self.nameTextfield.text, __objc_no)), "text"))
,但是为什么要前面这一长串呢?这是为了在编译期防止你引用了错误的属性,比如你写了1
@"text"
,编译器就会给出提示说这个属性不存在。clever.1
@RAC(self.nameTextField, somethingNotexist)
RAC
我们来看前半部分,也就是RAC的展开:
[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self.nameTextfield) nilValue:0][@"text"] = ...
RAC宏第一个参数是target,也就是需要绑定的对象;第二个参数是keyPath, 也就是对象中需要绑定的属性名。RAC实际上是创建了一个RACSubscriptingAssignmentTrampoline对象,并调用其
方法。1
setObject:forKeyedSubscript:
// RACSubscriptingAssignmentTrampoline.m
- (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath {
[signal setKeyPath:keyPath onObject:self.target nilValue:self.nilValue];
}
// RACSignal+Operations.m
- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue {
// ...
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
// ...
__strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr;
[object setValue:x ?: nilValue forKeyPath:keyPath];
} error:^(NSError *error) {
// ...
[disposable dispose];
} completed:^{
[disposable dispose];
}];
// ...
}
可见RAC的下标set操作对右边的信号调用了subscribeNext, 并在所有的next event中通过
修改属性的值。在这个例子里,也就相当于1
object setValue:forKeyPath
1
[self.nameTextField setValue:x forKeyPath: @"text"]
RACObserve
RACObserve的展开:
({ __weak id target_ = (self); [target_ rac_valuesForKeyPath:@("name") observer:self]; });
rac_valuesForKeyPath:observer:调用了另一个方法:
// NSObject+RACPropertySubscribing.m
- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver {
// ...
return [[[RACSignal
createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
//...
return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
[subscriber sendNext:RACTuplePack(value, change)];
}];
}]
takeUntil:deallocSignal]
setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", self.rac_description, keyPath, (unsigned long)options, strongObserver.rac_description];
// ...
}
这个方法创建了一个RACSignal, 这个Signal在keypath发生变化时会发出“通知”. 这个Signal由RACKVOTrampoline实现,RACKVOTrampoline封装了KVO,将在keypath发生变化时发送信号。
// NSObject+RACKVOWrapper.m
- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block {
// ...
RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:strongObserver keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) {
// ... 当KVO发生后进入这里,这里会调用前面传入的block
}
}
rac_textSignal
知道了
和1
RAC
,接下来我们来看1
RACObserve
的实现:1
rac_textSignal
// UITextField+RACSignalSupport.m
- (RACSignal *)rac_textSignal {
@weakify(self);
return [[[[[RACSignal
defer:^{
@strongify(self);
return [RACSignal return:self];
}]
concat:[self rac_signalForControlEvents:UIControlEventEditingChanged | UIControlEventEditingDidBegin]]
map:^(UITextField *x) {
return x.text;
}]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:@"%@ -rac_textSignal", self.rac_description];
}
可见rac_textSignal监控UIControlEventEditingChanged | UIControlEventEditingDidBegin事件,subscriber将获得textfield.text
weakify 和strongify
RAC中常见的
和 1
@weakify
展开如下:1
@strongify
@weakify
@autoreleasepool {} __attribute__((objc_gc(weak))) __typeof__(self) self_weak_ = (self);;
@strongify
__typeof__(self) self = self_weak_;
为了看起来美观,用了一个
的hack,使得宏的前面可以用1
autoreleasepool
,有点意思。1
@
textView
前面
的1
Textfield
使用监控control events的方式来获取变化。但是1
rac_textSignal
并没有这些event。所以RAC使用了delegate的方式来实现1
UITextView
的1
UITextview
。但是又有一个问题:如果想同时使用delegate怎么办呢?1
rac_textSignal
delegate forwardInvocation
RACDelegateProxy会用forwardInvocation的方式发送给self.delegate 所以如果需要同时使用,先设置delegate,再使用rac_textSignal即可。
// UITextView+RACSignalSupport.m
- (RACSignal *)rac_textSignal {
@weakify(self);
RACSignal *signal = [[[[[RACSignal
defer:^{
@strongify(self);
return [RACSignal return:RACTuplePack(self)];
}]
concat:[self.rac_delegateProxy signalForSelector:@selector(textViewDidChange:)]]
reduceEach:^(UITextView *x) {
return x.text;
}]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:@"%@ -rac_textSignal", self.rac_description];
RACUseDelegateProxy(self);
return signal;
}
是RAC中一个重要的方法,它的作用简单来说就是勾住一个方法,当这个方法被调用时把所有的参数放在一个tuple里,当做信号发出来。因此,1
signalForSelector
把1
RACUseDelegateProxy(self)
的1
UITextView
指向1
delegate
,而1
RACDelegateProxy
就可以捕捉所有原本调用1
RACDelegateProxy signalForSelector:@selector(textViewDidChange:)
的方法,并发出1
delegate
实例作为信号,并在1
textView
中获得其1
reduceEach
属性。1
text
参考
关于ReactiveCocoa:
http://cocoasamurai.blogspot.jp/2013/03/basic-mvvm-with-reactivecocoa.html
http://www.raywenderlich.com/62699/reactivecocoa-tutorial-pt1