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地址下载。效果图如下:

image1.png


项目结构图如下:


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<RACSubscriber> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容