论RAC+MVVM 模式下的网络请求

首先我简单的说一下,RAC、MVVM 是啥。

RAC:即ReactiveCocoa 是一套函数式编程的框架,的确很好用,可以节省很多开发时间。当下Swift也提供很多类似的高阶函数,学习一下还是很有必要的,我这里给几个链接有兴趣的可以看一下。
https://www.jianshu.com/p/87ef6720a096
https://www.jianshu.com/p/e10e5ca413b7

MVVM:由MVC演变而来的,为Controller 减负的设计模式。

RAC在MVVM设计中,充当胶水的角色。

1、数据绑定:作用View和ViewModel之间,但ViewModel中数据因为用户的操作变化时,View更改马上更新。
2、网络请求: 把网络请求封装成Command,在View层触发网络访问
3、Model 数据变化的时候,更新ViewModel数据

逻辑关系请看下图:


rac.png

这里只是简单的描述了一下RAC+MVVM,下面重点介绍一下其中网络请求的部分。在实际开发过程中,网络请求部分与以前的block后调方式有细微的差别。我这里用信号的方式返回数据,View层,通过Command 命令订阅返回数据,这里封装成了一个工具:FMARCNetwork(https://github.com/zhufaming/ZFMRACNetwork)

简单的思维导图:


shiwei.png

工具使用的主要三方框架:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

target 'ZFMRACNetwork' do
   
    #AFN -->不解释
    pod 'AFNetworking'
 
    #ReactiveCoca  --> ReactiveCocoa 对OC的支持
    pod 'ReactiveObjC', '~> 3.1.0'
   
    #网络提示--> 网络监听显示,主要是展示状态
    pod 'JDStatusBarNotification', '~> 1.5.6'
   
    #提示 -->主要的提示库
    pod 'MBProgressHUD', '~> 1.1.0' 
 end

项目结构:


jiegou.png

ZFMRACNetworkTool.h : 工具引用.h文件,使用工具,#import 一下就可以了
FMHttpConstant.h 网络请求相关的宏定义
FMHttpRequest.h 请求相关的参数配置
FMHttpResonse.h 网络请求响应的,信号返回的数据模型
FMARCNetwork 网络请求主要的工具类

单例设计工具类:

@interface FMARCNetwork : NSObject

+(instancetype) sharedInstance;
/**
 网络请求,返回信号
 按照, FMHttpRequest 参数化设置
 @param req FMHttpRequest
 @return RACSignal
 */
- (RACSignal *)requestNetworkData:(FMHttpRequest *)req;

/**
 网络请你,简便方案

 @param path 请求路径 --- 基本链接,请在 FMHttpRConstant.h 文件中设置
 @param params 参数字典
 @return RACSignal
 */
- (RACSignal *)requestSimpleNetworkPath:(NSString *)path params:(NSDictionary *)params;


/**
 文件上传、可以当个文件、也可以多个文件

 @param path 文件上传服务器地址,这里单独给出来,是因为很大部分图片服务器和业务服务器不是同一个
 @param params 参数 没有可传 @{}
 @param fileDatas NSData 数组
 @param name 指定数据关联的名称
 @return RACSignal
 */
- (RACSignal *)uploadNetworkPath:(NSString *)path params:(NSDictionary *)params fileDatas:(NSArray<NSData *> *)fileDatas name:(NSString *)name mimeType:(NSString *)mimeType;


@end

方法简介:
requestNetworkData : 通用请求,可以通过FMHttpRequest 配置请求的方式:Get or Post

requestSimpleNetworkPath: 简单的网络请求,传url 当然是字符串了,字典参数,默认Post 请求。这样设计的目的其实现在很少有用get 的请求了。

uploadNetworkPath: 文件或图片的上传。

实现:

- (RACSignal *)requestNetworkData:(FMHttpRequest *)req{
     /// request 必须的有值
    if (!req) return [RACSignal error:[NSError errorWithDomain:HTTPServiceErrorDomain code:-1 userInfo:nil]];
    
    @weakify(self);
    /// 创建信号
    RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        @strongify(self);
        /// 获取request
        
        //如果需要 额外的参数,请追加到参数字典里面去
        //ExtendsParameters *exParams = req.extendsParameters;
        
        NSError *serializationError = nil;
        NSMutableURLRequest *request = [self.manager.requestSerializer requestWithMethod:req.method URLString:[[NSURL URLWithString:req.path relativeToURL:[NSURL URLWithString:BaseUrl] ] absoluteString] parameters:req.parameters error:&serializationError];
        
        if (serializationError) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.manager.completionQueue ?: dispatch_get_main_queue(), ^{
                [subscriber sendError:serializationError];
            });
#pragma clang diagnostic pop
            return [RACDisposable disposableWithBlock:^{
            }];
        }
        /// 获取请求任务
        __block NSURLSessionDataTask *task = nil;
        task = [self.manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
             @strongify(self);
            if (error) {
                NSError *parseError = [self errorFromRequestWithTask:task httpResponse:(NSHTTPURLResponse *)response responseObject:responseObject error:error];
    
                NSInteger code = [parseError.userInfo[HTTPServiceErrorHTTPStatusCodeKey] integerValue];
                NSString *msgStr = parseError.userInfo[HTTPServiceErrorDescriptionKey];
                //初始化、返回数据模型
                FMHttpResonse *response = [[FMHttpResonse alloc] initWithResponseError:parseError code:code msg:msgStr];
                //同样也返回到,调用的地址,也可处理,自己选择
                [subscriber sendNext:response];
                //[subscriber sendError:parseError];
                [subscriber sendCompleted];
                //错误可以在此处处理---比如加入自己弹窗,主要是服务器错误、和请求超时、网络开小差
                [self showMsgtext:msgStr];
                
            } else {
              
                /// 判断
                NSInteger statusCode = [responseObject[HTTPServiceResponseCodeKey] integerValue];
                if (statusCode == HTTPResponseCodeSuccess) {
                    FMHttpResonse *response = [[FMHttpResonse alloc] initWithResponseSuccess:responseObject[HTTPServiceResponseDataKey] code:statusCode];
                   
                    [subscriber sendNext:response];
                    [subscriber sendCompleted];
                    
                }else{
                    if (statusCode == HTTPResponseCodeNotLogin) {
                        //可以在此处理需要登录的逻辑、比如说弹出登录框,但是,一般请求某个 api 判断了是否需要登录就不会进入
                        //如果进入可一做错误处理
                        NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
                        userInfo[HTTPServiceErrorHTTPStatusCodeKey] = @(statusCode);
                        userInfo[HTTPServiceErrorDescriptionKey] = @"请登录!";
                        
                        NSError *noLoginError = [NSError errorWithDomain:HTTPServiceErrorDomain code:statusCode userInfo:userInfo];
                        
                        FMHttpResonse *response = [[FMHttpResonse alloc] initWithResponseError:noLoginError code:statusCode msg:@"请登录!"];
                        [subscriber sendNext:response];
                        [subscriber sendCompleted];
                        //错误提示
                        [self showMsgtext:@"请登录!"];
                        
                    }else{
                        NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
                        userInfo[HTTPServiceErrorResponseCodeKey] = @(statusCode);
                        //取出服务给的提示
                        NSString *msgTips = responseObject[HTTPServiceResponseMsgKey];
                        //服务器没有返回,错误信息
                        if ((msgTips.length == 0 || msgTips == nil || [msgTips isKindOfClass:[NSNull class]])) {
                            msgTips = @"服务器出错了,请稍后重试~";
                        }
                        
                        userInfo[HTTPServiceErrorMessagesKey] = msgTips;
                        if (task.currentRequest.URL != nil) userInfo[HTTPServiceErrorRequestURLKey] = task.currentRequest.URL.absoluteString;
                        if (task.error != nil) userInfo[NSUnderlyingErrorKey] = task.error;
                        NSError *requestError = [NSError errorWithDomain:HTTPServiceErrorDomain code:statusCode userInfo:userInfo];
                        //错误信息反馈回去了、可以在此做响应的弹窗处理,展示出服务器给我们的信息
                        FMHttpResonse *response = [[FMHttpResonse alloc] initWithResponseError:requestError code:statusCode msg:msgTips];
                
                        [subscriber sendNext:response];
                        [subscriber sendCompleted];
                        //错误处理
                        [self showMsgtext:msgTips];
                    }
                }
            }
        }];
        
        /// 开启请求任务
        [task resume];
        return [RACDisposable disposableWithBlock:^{
            [task cancel];
        }];
    }];
    return [signal replayLazily]; //多次订阅同样的信号,执行一次
}

如何使用:给一个简单的例子,也在项目中,详情请git

- (IBAction)correctAction:(UIButton *)sender {
    /// 1. 配置参数
    NSMutableDictionary *easyDict = [NSMutableDictionary dictionary];
    /// 2. 配置参数模型 #define MH_GET_LIVE_ROOM_LIST  @"Room/GetHotLive_v2"
    FMHttpRequest *req = [FMHttpRequest urlParametersWithMethod:HTTTP_METHOD_POST path:@"Room/GetHotLive_v2" parameters:easyDict];
    
    
    _reqSignal = [[FMARCNetwork sharedInstance] requestNetworkData:req];
    
    [_reqSignal subscribeNext:^(FMHttpResonse *response) {
        if (response.isSuccess) {
            NSLog(@"--%@",response.reqResult);
            
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response.reqResult options:NSJSONWritingPrettyPrinted error:nil];
            
            NSString * str = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
            
            self.textV.text = str;
        }
        
    }];
}
end@2x.png

首先感谢大家浏览此文章。
网络请求可以直接下载使用:如果对您有帮助,请求点赞或喜欢❤️❤️,如果你有个github 账号,请不吝给❤️,谢谢。

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

推荐阅读更多精彩内容