很早就开始关注ReactiveCocoa了,前段时间决定把它加入到项目中,理由如下
- 熟悉响应式编程(函数式编程)模式的好时机
- 整个框架经过0.0.1版本到2.3.1的迭代已经相对成熟
- 反正我现在一个开发,不用考虑其他人看不懂的情况
- MVVM模式的尝试
从开始了解ReactiveCocoa
到现在,有时候总感觉没有完全利用好,比如
@weakify(self);
[[self.nextButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
@strongify(self);
[self performSegueWithIdentifier:@"Captcha" sender:nil];
}];
这样单一的信号传递和部署,没有和其他信号有任何的联系,总感觉和action-target
模式没什么区别,觉得这偏离了响应式编程的本意,或许这样说有点极端。
先说说很常用的验证码倒计时功能,用RAC来实现几乎一气呵成
点击按钮可以重新开始倒计时
@weakify(self);
RACSignal *timeSignal = [[[[[RACSignal interval:1.0f onScheduler:[RACScheduler mainThreadScheduler]] take:numberLimit] startWith:@(1)] map:^id(NSDate *date) {
@strongify(self);
if (number == 0) {
[self.timeButton setTitle:@"重新发送" forState:UIControlStateNormal];
return @YES;
}
else{
self.timeButton.titleLabel.text = [NSString stringWithFormat:@"%d", number--];
return @NO;
}
}] takeUntil:self.rac_willDeallocSignal];
self.timeButton.rac_command = [[RACCommand alloc]initWithEnabled:timeSignal signalBlock:^RACSignal *(id input) {
number = numberLimit;
return timeSignal;
}];
- 利用
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler
方法返回一个在主线程上间隔一秒执行一次的signal
,RAC内部是利用dispatch_source_set_timer
dispatch_source_set_event_handler
dispatch_resume
这一系列的GCD方法来实现的。注意这个signal
所sendnext
的值是当前日期的NSDate
对象,不过这个值对于整个功能是没有用处的。 - (instancetype)take:(NSUInteger)count
取前numberLimit
次sendnext
的值,相当于我们需要倒计时多久- 通过
- (instancetype)startWith:(id)value
修改第一次sendnext
的值,并且立即sendnext
这个值。理论上对于sendnext
的值是不需要处理的,原因是+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler
所返回的signal
会停顿1秒才执行,利用startWith
来立即开始倒计时罢了,所以说startWith:
的参数值是什么不重要 - 利用
- (instancetype)map:(id (^)(id value))block
修改sendnext
最终的值返回@YES
或者@NO
- (RACSignal *)takeUntil:(RACSignal *)signalTrigger
意思是当这个VC的即将dealloc
的时候停止倒计时,RAC内部其实就是sendCompleted
。- 最后把
timeSignal
和timeButton.rac_command
绑定判断按钮是否可用,并且启动timeSignal
。按钮被点击的时候恢复number
的值,return buttonSignal
来重新启动倒计时timeSignal
如果RAC利用得当,几乎可以抛弃写自定义的委托协议。想象一下:当aVC presentViewController
到bVC,然后 dismissViewControllerAnimated
bVC的时候需要在aVC中做一些事情,这时候通常用委托协议可以解决这种问题,比较麻烦。利用RAC实现就显得非常简洁明了
其中的关键方法是- (RACSignal *)rac_signalForSelector:(SEL)selector
,基本使用是这样的
[[self rac_signalForSelector:@selector(dismiss:)] subscribeNext:^(id x) {
NSLog(@"%s", __func__);
}];
- (IBAction)dismiss:(id)sender{
[self dismissViewControllerAnimated:YES completion:NULL];
}
当dismiss:
方法被执行后信号就会被部署
信号什么时候部署可以由我们来决定,既然需要在aVC中处理一些事,那么就应该想办法在aVC中来部署信号。这时候就需要把信号作为bVC的属性
bVC.h
#import <UIKit/UIKit.h>
@interface RACDelegateViewController : UIViewController
@property (nonatomic, strong) RACSignal *delegateSignal;
@end
部署信号
aVC.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.destinationViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)segue.destinationViewController;
RACDelegateViewController *delegateVC = (RACDelegateViewController *)nav.topViewController;
[delegateVC.delegateSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
}
还有最后一步,生成delegateSignal
bVC.m
- (void)awakeFromNib{
[super awakeFromNib];
self.delegateSignal = [self rac_signalForSelector:@selector(dismiss:)];
}
注意:千万不要在- (void)viewDidLoad;
去生成delegateSignal
,这会导致在执行subscribeNext
的时候delegateSignal
还是空的。
还不够完美对不对,aVC中接收的数据一直都是UIBarButtonItem对象,想把bVC中的数据传递到aVC中应该如何实现。还必须在信号上做一些调整,利用 - (RACSignal *)then:(RACSignal * (^)(void))block;
这个方法功能是忽略接收者所有的next
,直到信号complete
返回一个新的RACSignal。看用法:
- (void)awakeFromNib{
[super awakeFromNib];
@weakify(self);
self.delegateSignal = [[self rac_signalForSelector:@selector(dismiss:)] then:^RACSignal *{
@strongify(self);
return [RACSignal return:self.array];
}];
}
当[self rac_signalForSelector:@selector(dismiss:)]
信号sendComplete
的时候,执行一个block,这个block必须返回一个不为空的新的RACSignal。当然还有其他方法,比如:map
这个信号:
- (void)awakeFromNib{
[super awakeFromNib];
@weakify(self);
self.delegateSignal = [[self rac_signalForSelector:@selector(dismiss:)] map:^id(id value) {
@strongify(self);
return self.array;
}];
}
对于需要监听协议方法的时候可以使用 - (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol
具体可以看我的笔记共享
前段时间看了objc中国
的两篇文章更轻量的 View Controllers和整洁的 Table View 代码,也试着把ReactiveCocoa
结合进UITableView
中
- KVO数据源数组
- (id)initWithCellIdentifier:(NSString *)aCellIdentifier
configureCellBlock:(CellConfigureBlock)aConfigureCellBlock{
self = [super init];
if (self) {
_cellIdentifier = aCellIdentifier;
_configureCellBlock = [aConfigureCellBlock copy];
_signal = RACObserve(self, dataSource);
}
return self;
}
- map数据源得到单一的数据模型
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellIdentifier
forIndexPath:indexPath];
if (self.configureCellBlock) {
@weakify(self);
self.configureCellBlock(cell, [self.signal map:^id(NSArray *array) {
@strongify(self);
return [self itemAtIndexPath:indexPath];
}]);
}
return cell;
}
- VC中的用法
self.model = [[DataSourceGeneralModel alloc] initWithCellIdentifier:CelIdentifier
configureCellBlock:^(TableViewCell *cell, RACSignal *signal) {
[cell configureCellWithSignal:signal];
}];
- cell中配置
- (void)configureCellWithSignal:(RACSignal *)signal{
@weakify(self);
[signal subscribeNext:^(Model *model) {
@strongify(self);
self.titleLabel.text = model.title;
self.detailLabel.text = model.detail;
}];
}
如果更新数据源数组,UITableView也会得到相应的更新,不用调用[self.tableView reloadData]
,像这样更新即可
self.model.dataSource = @[model1];
以上例子可以在github下载
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。