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

相关阅读:

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网络请求重构 的相关内容,希望对大家有所帮助!

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索rac cocoa、raccocoa视频教程、raccocoa、raccommand 网络请求、rac网络请求,以便于您获取更多的相关知识。

时间: 2024-09-20 21:32:40

ReactiveCocoa代码实践之-RAC网络请求重构_Android的相关文章

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

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

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

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

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

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

ReactiveCocoa代码实践之-更多思考

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

Android 经典笔记之八:网络请求数据基础介绍

关于网络请求数据总结 目录介绍 1.Http请求与响应 1.1 Http请求包的结构 1.2 HTTP响应包结构 2.Http请求方式 3.Get和Post的比较 3.1 get请求 3.2 post请求 3.3 其他区别 3.4 网络心声 4.Http响应方式 5.同步和异步 6.Http缓存机制讲解 6.1 request请求字段含义 6.2 response响应字段含义 6.3 缓存机制逻辑图 0.本人写的综合案例 案例 说明及截图 模块:新闻,音乐,视频,图片,唐诗宋词,快递,天气,记事

android 网络请求库volley方法详解_Android

使用volley进行网络请求:需先将volley包导入androidstudio中 File下的Project Structrue,点加号导包   volley网络请求步骤:       1. 创建请求队列       RequestQueue queue = Volley.newRequestQueue(this);       2.创建请求对象(3种)                            StringRequest request = new StringRequest("

网络请求框架 Retrofit 2 使用入门

本文讲的是网络请求框架 Retrofit 2 使用入门, 你将要创造什么 Retrofit 是什么? Retrofit 是一个用于 Android 和 Java 平台的类型安全的网络请求框架.Retrofit 通过将 API 抽象成 Java 接口而让我们连接到 REST web 服务变得很轻松.在这个教程里,我会向你介绍如何使用这个 Android 上最受欢迎和经常推荐的网络请求库之一. 这个强大的库可以很简单的把返回的 JSON 或者 XML 数据解析成简单 Java 对象(POJO).GE

Android 网络请求详解

我们知道大多数的 Android 应用程序都是通过和服务器进行交互来获取数据的.如果使用 HTTP 协议来发送和接收网络数据,就免不了使用 HttpURLConnection 和 HttpClient,而 Android 中主要提供了上述两种方式来进行 HTTP 操作.并且这两种方式都支持 HTTPS 协议.以流的形式进行上传和下载.配置超时时间.IPv6.以及连接池等功能. 但是 Googl e发布 6.0 版本的时候声明原生剔除 HttpClient,但是笔者认为 HttpClient 会提

网络请求有问题,一直调试不了

问题描述 网络请求有问题,一直调试不了 10-17 22:07:04.490: D/libc(21273): [NET] android_getaddrinfofornet+,hn 23(0x7363686f6f6c75),sn(),hints(known),family 0,flags 4 10-17 22:07:04.490: D/libc(21273): [NET] android_getaddrinfofornet-, err=8 10-17 22:07:04.490: D/libc(2