ReactiveCocoa代码实践之-RAC网络请求重构

相关阅读:

ReactiveCocoa代码实践之-UI组件的RAC信号操作

ReactiveCocoa代码实践之-更多思考

前言

•RAC相比以往的开发模式主要有以下优点:提供了统一的消息传递机制;提供了多种奇妙且高效的信号操作方法;配合MVVM设计模式和RAC宏绑定减少多端依赖。

•RAC的理论知识非常深厚,包含有FRP,高阶函数,冷信号与热信号,RAC Operation,信号的生命周期等,这些文档里都有介绍。 但是由于RAC本身的特性,可能会听上去容易上手难。

•本文还是从一个比较接地气的角度开始的。因为现在要做一个完美100%的全项目ReactiveCocoa架构基本不太现实,大多数项目都会有很多历史包袱,我们只能渐渐的向RAC靠拢,将一段段恶心的代码重构,使逻辑功能更加清晰。

本节主要我之前对网络请求的重构的一个简单记录。

一.普通请求重构

旧代码结构图:

之前的代码控制器中都是一个个需要连接网络的方法中直接调用service的请求方法并获取回调,属于常规做法。

// controller.m ************************************
// 控制器中的某一处方法
- (void)requestForTop{
[MDSBezelActivityView activityViewForView:self.view withLabel:@"加载中..."];
// 直接调用service里的请求方法
[SXFeedbackService requestForFeedbackSummarySuccess:^(NSDictionary *result) {
[MDSBezelActivityView removeView];
// 成功后相关处理
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[MDSBezelActivityView removeView];
// 失败后相关处理
}];
}

重构后结构图:

使用RAC改写后,controller不会直接调用service,controller通过控制一个个command的执行与否来达到发请求的目的。得到数据后绑定的值一旦发生改变,会来到RACObserve的回调方法。并且如果请求失败,也会以错误信号的方式传递到execute的subscribeError回调方法里。 executing可以用来监听命令是否执行完。

// controller.m ************************************
@property(nonatomic,strong)SXFeedbackMainViewModel *viewModel;
- (void)viewDidLoad{
[self addRACObserve];
}
// 在页面初次加载时设置绑定
- (void)addRACObserve{
@weakify(self);
[[RACObserve(self.viewModel, topNumEntity) skip:] subscribeNext:^(id x) {
@strongify(self);
// 绑定viewModel的值一旦改变来到这里。
}];
}
// 原本用来发请求的地方
- (void)requestForTop
{
[[self.viewModel.fetchFeedbackSummaryCommand execute:nil] subscribeError:^(NSError *error) {
// 对错误的处理
}];
[[self.viewModel.fetchFeedbackSummaryCommand.executing skip:] subscribeNext:^(NSNumber *executing) {
if ([executing boolValue]) {
[MDSBezelActivityView activityViewForView:self.view withLabel:@"加载中..."];
}else{
[MDSBezelActivityView removeView];
}
}];
}
// viewModel.m ************************************
- (instancetype)init
{
self = [super init];
[self setupRACCommand];
return self;
}
// 初始化设定一个指令用来打开某个请求
- (void) setupRACCommand
{
@weakify(self);
_fetchFeedbackSummaryCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 这里面更彻底的方法是直接将请求写成一个operation,但是大多数项目的网络层应该都有manager或是签名等原因想直接改成那种结构可能比较复杂 ,所以这里面的代码像是RAC和直接请求的结合。
[SXMerchantAutorityService requestForFeedbackSummarySuccess:^(NSDictionary *result) {
@strongify(self);
// 成功回调后做的相关操作
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
}];
return nil;
}];
}];
}

二.需要传参数的请求

上面是普通的请求,就是请求地址是写死或者是从全局变量中拼接参数的。 如果需要传入若干参数的话controller无法直接接触到service,所以需要以viewModel作为媒介传值,有两种传值方法。

1.通过viewModel的属性

这种方法可用于参数少,一个或两个的。直接在viewModel里加上一些属性,然后controller在适当的时候给这个属性赋值。 在viewModel中的RACCommand中调用service方法需要参数时直接从自己的属性取。

// controller.m ************************************
self.viewModel.isAccess = self.isAccess;
[self requestForTop];
// viewModel.h ************************************
// input参数
/**
* 是美团还是点评
*/
@property(nonatomic, assign) BOOL isAccess;
// viewModel.m ************************************
_fetchFeedbackSummaryCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[SXMerchantAutorityService requestForFeedbackSummaryWithType:self.isAccess success:^(NSDictionary *result) {
// 成功
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// 失败
}];
return nil;
}];
}];

如果是用RAC宏设置viewModel和controller的某些属性绑定,那也可以省去手动给viewModel的set方法赋值这一步。(董铂然博客园)

2.通过execute方法参数传值

这种方法适用于参数较多的情况无法一一列为viewModel的属性。 这时候建议设置一个对象模型,然后在execute方法前将这个模型建立好并赋值,然后作为参数传入。

比如这种常见的列表类的具有多个参数的请求方法:

// service.h ************************************
/**
* 获取评价列表
*/
+ (void)requestForFeedbacklistWithSource:(BOOL)isFromWeb
dealid:(NSInteger)dealid
poiid:(NSInteger)poiid
labelName:(NSString *)labelName
type:(NSString *)type
readStatus:(NSString *)readStatus
replyStatus:(NSString *)replyStatus
limit:(NSNumber *)limit
offset:(NSNumber *)offset
success:(void(^)(NSDictionary *result))success
failure:(void(^)(AFHTTPRequestOperation *operation, NSError *error))failure; 

在controller的发请求方法中旧方法就是直接调用service的请求接口,这里不再列出,下面列出RAC的写法。

// controller.m ************************************
- (void)requestForDataWithType:(int)type
{
// ------给RACComand传入一个input模型。
SXFeedbackListRequestModel *input = [SXFeedbackListRequestModel new];
input.replyStatus = self.replyStatus; // 这里也可以写成一个工厂方法
input.readStatus = self.readStatus;
input.isMeituan = self.isMeituan;
input.dealid = self.dealid;
input.poiid = self.poiid;
input.type = self.type;
input.labelName = labelName;
input.offset = @(self.offset);
input.limit = @();
// 上面的input在这里作为参数传入
[[self.viewModel.fetchFeedbackListCommand execute:input] subscribeNext:^(id x) {
// ------这里处理正确的操作。
} error:^(NSError *error) {
// ------这里处理失败的操作。
}];
}
// viewModel.m ************************************
- (void) setupRACCommand
{
_fetchFeedbackListCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(SXFeedbackListRequestModel *input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 用前面execute传入的参数会传到这个地方
[SXMerchantAutorityService requestForFeedbacklistWithSource:input.isFormWeb dealid:input.dealid poiid:input.poiid labelName:input.labelName type:input.type readStatus:input.readStatus replyStatus:input.replyStatus limit:input.limit offset:input.offset success:^(NSDictionary *result) {
@strongify(self);
// 一些操作
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
}];
return nil;
}];
}];
}

可能会觉得在这个command中要把之前的模型的每一个属性都扒出来传到参数里行为有点冗余。 可以将之前service里的那个参数很多的方法改写成只需要传入一个模型。然后command这里就可以直接传入模型了,反正在方法内部再取出来也不麻烦。我这边考虑到了其他非RAC地方的兼容性就没有改了。

三.所有请求完成才消除toast

这里是一个类似于请求combo的概念。所有的请求全部结束后才消除加载中的progressHUD ,如果在普通的架构下可用dispatch调度组来解决,但是RAC实现这个功能非常简单,主要方法是通过executing信号来判断一个命令的的状态,然后使用combineLatest操作来监听多个command的状态,combineLatest操作的特征是监听的多个信号只要有一个改变了就把所有信号组成一个tuple返回。

// 监听executing
RACSignal *hud = [RACSignal combineLatest:@[self.viewModel.fetchFeedbackListCommand.executing,self.viewModel.fetchFeedbackSummaryCommand.executing]];
[hud subscribeNext:^(RACTuple *x) {
if (![x.first boolValue]&&![x.second boolValue]) {
[MDSBezelActivityView removeView];
}else{
[MDSBezelActivityView activityViewForView:self.view withLabel:@"加载中..."];
}
}];

这个建议和之前RACObserve写在一起。 也可以改成filter的写法。

// 可以把加载HUD的代码写在最前面,然后后面直接控制消除HUD
[[hud filter:^BOOL(RACTuple *x) {
return ![x.first boolValue]&&![x.second boolValue];
}] subscribeNext:^(id x) {
[MDSBezelActivityView removeView];
}]; 

还有另一种方法也可以实现这种需求,rac_liftSelector这个方法是只有所有数组中的信号都发出sendNext信号时才会调用那个@selector的方法,并且这个方法的三个参数分别就是那三个sendNext发的。 所有的都回来了再统一打包,这主要适用于三个请求都是异步没有依赖关系。

@weakify(self);
[[self rac_liftSelector:@selector(doWithA:withB:withC) withSignalsFromArray:@[signalA,signalB,signalC]] subscribeError:^(NSError *error) {
@strongify(self);
[MDSBezelActivityView removeView];
} completed:^{
[MDSBezelActivityView removeView];
}]; 

combineLatest和liftselector两种combo的方法有一定的区别,具体的使用可以结合需求。前者是每一个请求回来了都会回调一下,后者是全部回来了再调用方法。(董铂然博客园)

四.结果数据的传递

如果是希望所有的请求都完成了所有数据都获得了,后再刷新界面,使用上面统一消除toast的方法时同样适合的。 把消除toast那行代码改成[self.tableVIew reloadData]或其他代码即可。

因为现在的主流是希望能够瘦身Controller, 所以一般也建议将业务逻辑、判断、计算、拼接字符串放在viewModel里,最后直接把需要的数据返回,控制器只负责得到干脆的数据后直接展示界面。 下面的例子是一个文本标签上文字的获得方法

// Controller.m ************************************
// ViewDidLoad
RAC(self.replyCountLabel,text) = RACObserve(self.viewModel, replyCountLabelTitle);
// ViewModel.m ************************************
_fetchNewsDetailCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
@strongify(self);
[self requestForNewsDetailSuccess:^(NSDictionary *result) {
// 这边省去一些判空代码
self.detailModel = [SXNewsDetailEntity detailWithDict:result[self.newsModel.docid]];
// 中间还有一些其他的操作省略
NSInteger count = [self.newsModel.replyCount intValue];
// 这里是直接把拼接好的标题返回,现实中还会遇到更复杂的逻辑
if ([self.newsModel.replyCount intValue] > ) {
self.replyCountBtnTitle = [NSString stringWithFormat:@"%.f万跟帖",count/.];
}else{
self.replyCountBtnTitle = [NSString stringWithFormat:@"%ld跟帖",count];
}
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
}];
return nil;
}];
}]; 

重构时可以将更多控制器的属性比如模型,或数组,放到viewModel里。 以前控制器里的self.replyModels 改成self.ViewModel.replyModels。

// ViewModel.h ************************************
/**
* 相似新闻
*/
@property(nonatomic,strong)NSArray *similarNews;
/**
* 搜索关键字
*/
@property(nonatomic,strong)NSArray *keywordSearch;
/**
* 获取搜索结果数组命令
*/
@property(nonatomic, strong) RACCommand *fetchNewsDetailCommand;
// ViewModel.m ************************************
// 某个command里调用发请求方法成功的回调内
self.similarNews = [SXSimilarNewsEntity objectArrayWithKeyValuesArray:result[self.newsModel.docid][@"relative_sys"]];
self.keywordSearch = result[self.newsModel.docid][@"keyword_search"];
[subscriber sendCompleted];
// Controller.m ************************************
// 随便拿了个方法举例
- (CGFloat )tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
switch (section) {
case :
return self.webView.height;
break;
case :
return self.viewModel.replyModels.count > ? : CGFLOAT_MIN;
break;
case :
return self.viewModel.similarNews.count > ? : CGFLOAT_MIN;
break;
default:
return CGFLOAT_MIN;
break;
}
}

合理的分离之后应该是Controller只有一些UI控件,ViewModel中存放模型属性,命令,和一些业务逻辑操作或判断的方法等。

对其中的一些demo代码感兴趣的可以fork下这里的代码 https://github.com/dsxNiubility/SXNews。以前是用土方法写了个小项目,现在旧代码移到了old分支,master分支上持续在做一些RAC相关的改动。

以上所述是小编给大家介绍的ReactiveCocoa代码实践之-RAC网络请求重构 的相关内容,希望对大家有所帮助!

(0)

相关推荐

  • ReactiveCocoa代码实践之-RAC网络请求重构

    相关阅读: ReactiveCocoa代码实践之-UI组件的RAC信号操作 ReactiveCocoa代码实践之-更多思考 前言 •RAC相比以往的开发模式主要有以下优点:提供了统一的消息传递机制:提供了多种奇妙且高效的信号操作方法:配合MVVM设计模式和RAC宏绑定减少多端依赖. •RAC的理论知识非常深厚,包含有FRP,高阶函数,冷信号与热信号,RAC Operation,信号的生命周期等,这些文档里都有介绍. 但是由于RAC本身的特性,可能会听上去容易上手难. •本文还是从一个比较接地气的

  • ReactiveCocoa代码实践之-更多思考

    相关阅读: ReactiveCocoa代码实践之-UI组件的RAC信号操作 ReactiveCocoa代码实践之-RAC网络请求重构 1. RACObserve()宏形参写法的区别 之前写代码考虑过 RACObserve(self.timeLabel , text) 和 RACObserve(self , timeLabel.text) 的区别. 因为这两种方法都是观察self.timeLabel.text的属性,并且都能实现功能.估计是作者原本用的其中一种后来对另一种也提供了支持,究竟有什么区

  • ReactiveCocoa代码实践之-UI组件的RAC信号操作

    相关阅读: ReactiveCocoa代码实践之-更多思考 ReactiveCocoa代码实践之-RAC网络请求重构这一节是自己对网络层的一些重构,本节是自己一些代码小实践做出的一些demo程序,基本涵盖大多数UI控件操作. 一.用UISlider实现调色板 假设我们现在做一个demo,上面有一个View用来展示颜色,下面有三个UISlider滑竿分别控制RGB的色值,随着不同滑竿的拖动上面view的颜色会随之改变. 可以先脑补一下不用RAC该怎么写. 如果使用RAC只需要将三个信号包装起来用适

  • 微信小程序 POST请求(网络请求)详解及实例代码

    微信小程序 POST请求 微信小程序开发中网络请求必不可少.GET.POST请求是最常用的.GET请求,POST请求的时候有好几个坑.我已经为大家填好了. <img src="http://img.blog.csdn.net/20161017170933243?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Cente

  • Android Xutils3网络请求的封装详解及实例代码

     Xutils3网络请求的封装详解 封装了一个Xutil3的网络请求工具类,分享给大家,本人水平有限,不足之处欢迎指出. 使用前先配置xutils3: 1.gradle中添加 compile 'org.xutils:xutils:3.3.40' 2.自定义Application /** * Created by Joe on 2016/9/25. */ public class MyApp extends Application { @Override public void onCreate(

  • React Native 使用Fetch发送网络请求的示例代码

    我们在项目中经常会用到HTTP请求来访问网络,HTTP(HTTPS)请求通常分为"GET"."PUT"."POST"."DELETE",如果不指定默认为GET请求. 在项目中我们常用到的一般为GET和POST两种请求方式,针对带参数的表单提交这类的请求,我们通常会使用POST的请求方式. 为了发出HTTP请求,我们需要使用到 React Native 提供的 Fetch API 来进行实现.要从任意地址获取内容的话,只需简单地

  • C#网络请求与JSON解析的示例代码

    最新学校的海康摄像头集控平台(网页端)不能在win10里登录,我寻思着拿海康的c# demo直接改. 首先得解决权限问题,每个教师任教不同年级,只能看到自己所在年级的设备,涉及到登录,在此记录一下C#中网络请求和数据处理的一些内容.大致流程为: 客户端发起登录请求: 服务端验证账号密码 返回json字符串,包含用户信息.平台配置等信息 客户端解析并初始化 一.发起GET请求 private string HttpGet(string api) { string serviceAddress =

  • iOS中多网络请求的线程安全详解

    前言 在iOS 网络编程有一种常见的场景是:我们需要并行处理二个请求并且在都成功后才能进行下一步处理.下面是部分常见的处理方式,但是在使用过程中也很容易出错: DispatchGroup:通过 GCD 机制将多个请求放到一个组内,然后通过 DispatchGroup.wait() 和 DispatchGroup.notify() 进行成功后的处理. OperationQueue:为每一个请求实例化一个 Operation 对象,然后将这些对象添加到 OperationQueue ,并且根据它们之

  • python编程之requests在网络请求中添加cookies参数方法详解

    哎,好久没有学习爬虫了,现在想要重新拾起来.发现之前学习爬虫有些粗糙,竟然连requests中添加cookies都没有掌握,惭愧.废话不宜多,直接上内容. 我们平时使用requests获取网络内容很简单,几行代码搞定了,例如: import requests res=requests.get("https://cloud.flyme.cn/browser/index.jsp") print res.content 你没有看错,真的只有三行代码.但是简单归简单,问题还是不少的. 首先,这

  • Android Retrofit和Rxjava的网络请求

    Android  Retrofit和Rxjava的网络请求 去年的时候好多公司就已经使用Rxjava和Retrofit了,最近自自己学习了一下,感觉真的是很好用,让自己的网络请求变得更简单了,而且封装性极强. 首先做一下准备工作,导入需要引用的文件 compile 'com.android.support:appcompat-v7:25.1.0' testCompile 'junit:junit:4.12' compile 'io.reactivex:rxjava:1.1.0' compile

随机推荐