ReactiveCocoa+MVVM实战

ReactiveCocoa是由github开发维护的一个开源框架,简称RAC,它采用的是函数响应式编程(FRP)技术,区别于Objective-c面相对象的编程思想。所以刚接触这类编程思想的理解起来会有一点变扭。

在iOS中使用RAC后代码可读可维护,结构清晰,RAC提供的事件流通过 Signal 和 SignalProducer 类型来表示, 统一了Cocoa用于事件和异步处理的常用模式,包括

  • 网络请求:RACScheduler:(创建子线程)
  • 遍历:map:
  • 代理:rac_signalForSelector:
  • block:
  • 通知:rac_addObserverForName:
  • KVO:rac_valuesAndChangesForKeyPath
  • 控件的响应事件链:rac_signalForControlEvents:
  • 监听文本框文字改变:rac_textSignal:

RAC语法如下:

@weakify(self);
[[[[[[[self requestAccessToTwitterSignal]
      then:^RACSignal *{
          @strongify(self)
          return self.searchText.rac_textSignal;
      }]
     filter:^BOOL(NSString *text) {
         @strongify(self)
         return [self isValidSearchText:text];
     }]
    throttle:0.5]
   flattenMap:^RACStream *(NSString *text) {
       @strongify(self)
       return [self signalForSearchWithText:text];
   }]
    deliverOn:[RACScheduler mainThreadScheduler]]
    subscribeNext:^(NSDictionary *jsonSearchResult) {
       NSArray *statuses = jsonSearchResult[@"statuses"];
       NSArray *tweets = [statuses linq_select:^id(id tweet) {
           return [RWTweet tweetWithStatus:tweet];
     }];
     [self.resultsViewController displayTweets:tweets];
 } error:^(NSError *error) {
     NSLog(@"An error occurred: %@", error);
 }];

这个例子就不用解释了,往下看会明白,其中所有的API都基于信号Signal的,常用API如下:

1.绑定
  • bind:绑定
2.映射
  • flattenMap:信号发出的值是信号,Block返回信号。
  • Map:信号发出的值不是信号,Block返回对象。
    他们用于把源信号内容映射成新的内容
3.组合
  • concat:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
  • then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
  • merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用
  • zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
  • combineLatest:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
  • reduce聚合:用于信号发出的内容是元组,把信号发出元组的值聚合成一个值
4.过滤
  • filter:过滤信号,使用它可以获取满足条件的信号.
  • ignore:忽略完某些值的信号.
  • distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
  • take:从开始一共取N次的信号
  • takeLast:取最后N次的信号,前提条件,订阅者必须调用完成,因为只有完成,就知道总共有多少信号.
  • takeUntil:(RACSignal *):获取信号直到某个信号执行完成
  • skip:(NSUInteger):跳过几个信号,不接受。
  • switchToLatest:用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号。
5.秩序
  • doNext: 执行Next之前,会先执行这个Block
  • doCompleted: 执行sendCompleted之前,会先执行这个Block
6.线程
  • deliverOn: 内容传递切换到制定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。
  • subscribeOn: 内容传递和副作用都会切换到制定线程中。
7.时间
  • timeout:超时,可以让一个信号在一定的时间后,自动报错。
  • interval 定时:每隔一段时间发出信号
  • delay 延迟发送next。
8.重复
  • retry重试 :只要失败,就会重新执行创建信号中的block,直到成功.
  • replay重放:当一个信号被多次订阅,反复播放内容
  • throttle节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。

下面基于RAC+MVVM写了一个demo,源码请点击github地址下载。效果图如下:

ReactiveCocoa+MVVM实战_第1张图片
image1.png


项目结构图如下:


ReactiveCocoa+MVVM实战_第2张图片
image.png

M:的代码

//UserModel.h
@interface UserModel : NSObject
@property (nonatomic, copy)   NSString *desc;
@property (nonatomic, copy)   NSString *name;
@property (nonatomic, copy)   NSString *avatar;
@property (nonatomic, copy)   NSString *alt;
+ (instancetype)userWithDict:(NSDictionary *)dic;
@end

//UserModel.m
@implementation UserModel
+ (instancetype)userWithDict:(NSDictionary *)dic
{
    //采用的笨方法选择需要的几个数据
    UserModel *book = [[UserModel alloc] init];
    book.name = dic[@"name"];
    book.desc = dic[@"desc"];
    book.avatar = dic[@"avatar"];
    book.alt = dic[@"alt"];
    return book;
}
@end

model里的代码很简单,仅有的一个方法就是字典转模型,没有用kvc与第三方转化。

V:的代码(V包括View与ViewController)

#import "ViewController.h"
#import "RequestViewModel.h"
#import "DetailController.h"

@interface ViewController ()

@property (nonatomic, weak)   UITableView *tableView;
@property (nonatomic, strong) RequestViewModel *requesViewModel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.title = @"RAC + MVVM";
    self.view.backgroundColor = [UIColor whiteColor];
    _requesViewModel = [[RequestViewModel alloc] init];
    
    UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)];
    tableView.dataSource = self.requesViewModel;
    tableView.delegate = self.requesViewModel;
    [self.view addSubview:tableView];
    self.tableView = tableView;
    
    _requesViewModel.selectCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(RACTuple *turple) {
        
        DetailController *detailVC = [[DetailController alloc] init];
        detailVC.sendObject = turple;
        [self.navigationController pushViewController:detailVC animated:YES];
        return [RACSignal empty];
    }];
    
    @weakify(self);
    [[[self.requesViewModel.reuqesCommand execute:nil]
     deliverOn:[RACScheduler mainThreadScheduler]]
     subscribeNext:^(NSArray *x) {
         @strongify(self);
         self.requesViewModel.models = x;
         [self.tableView reloadData];
    }];
}

@end

首先实例话了一个VM对象_requesViewModel,然后创建UI,将代理方法都设置为vm对象,从而减少了C代码量。接着绑定selectCommand,这个命令实现跳转控制器。最后执行reuqesCommand,通过execute方法激活信号,通过deliverOn方法转移到主线程用于刷新UI,然后通过subscribeNext方法订阅了信号,传的block参数需要发送sendNext方法才会被激活。其中涉及到RACTuple类,它就是集合类,这里做了NSArray的事。

VM:的代码


#import "RequestViewModel.h"
#import "UserModel.h"
#import "AFNetWorkHelp.h"
#import "LXTableViewCell.h"

@implementation RequestViewModel

- (instancetype)init
{
    if (self = [super init]) {
        [self initialBind];
    }
    return self;
}

- (void)initialBind
{
    _reuqesCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        
        RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
            
            NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
            parameters[@"q"] = @"iOS";
            parameters[@"count"] = @"30";
            NSString *urlStr = @"https://api.douban.com/v2/user";
            
            [AFNetWorkHelp getWithUrlString:urlStr withParam:parameters success:^(NSDictionary *responseDic) {
                
                // 请求成功调用
                // 把数据用信号传递出去
                [subscriber sendNext:responseDic];
                [subscriber sendCompleted];
                LogBlue(@"%@", responseDic);

            } failure:^(NSError *error) {
                
            }];
            
            return nil;
        }];
        
        // 在返回数据信号时,把数据中的字典映射成模型信号,传递出去
        return [requestSignal map:^id(NSDictionary *value) {
            NSArray *dictArr = value[@"users"];
            
            // 字典转模型,遍历字典中的所有元素,全部映射成模型,并且生成数组
#if 1       
            //这是正常方法
            NSArray *modelArr = [dictArr dicToModel:^id(id user) {
                return [UserModel userWithDict:user];
            }];
#else
            //这是ARC提供的遍历方法,简单
            NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) {
                return [UserModel userWithDict:value];
            }] array];
#endif
            return modelArr;
        }];
    }];
}

#pragma mark - UITableViewDelegate
//省略部分代理方法
......
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    RACTuple *turple = [RACTuple tupleWithObjects:self.models[indexPath.row], indexPath, nil];
    [self.selectCommand execute:turple];
}

@end

初始化本类的对象时就创建了_reuqesCommand信号,用于暴露给V去激活。在这个信号的block中做了两件事,第一是用信号requestSignal执行网络请求,套用了AFNetworking框架。第二是在return前将得到的网络数据转化为模型数组。

当获取到网络数据运行[subscriber sendNext:responseDic];是发送信号,后才会执行return [requestSignal map:^id(NSDictionary *value)后的block,收到信号。



源码请点击github地址下载。


QQ:2239344645 我的github

你可能感兴趣的:(ReactiveCocoa+MVVM实战)