iOS程序设计心得总结(二)网络层设计

来源:互联网 发布:淘宝店铺四个钻石 编辑:程序博客网 时间:2024/05/22 03:23

网络层设计 

  在做网络层设计时,我一般把设计的重心主要集中在请求、连接的统一管理、参数及回调的统一控制、连接层的分离,以及网络层的"尽力而为"设计上,这些都是在网络层的内部去做一些设计。最终的目的,想要达到是竭尽全力的减轻上层(应用层)负担,尽可能或是完全的做到对上层的完全透明与分离,对上层暴露统一的接口,上层仅仅作为调用(使用)者的身份,完全不用关心网络层的连接、以及相关业务、数据的处理,它就关心自己负责的界面交互及业务串联就好了。

  网络层主要有两个职责:1.数据请求,与服务器建立数据通道;2.数据获得后,进行数据处理,然后存储,反馈。下面,两个职责分开来说

一.数据请求

1.基本设计

首先,我们可以对网络层细分为业务层和连接层


与服务器打交道一般也就是采用http请求或socket连接,这个层面即是我前面所谓的连接层。这个层面的封装实现其实别人都为我们做好了,ASIHttpRequest、AFNetworking、GCDAsyncSocket这些著名的第三方库就是对这个层面的封装,我们直接拿来使用即可,我们要考虑的是这些库在使用层面上的设计(网络业务层)。下面,是我一般在项目中对网络层的组织与实现(dataproxy这个名字可能有歧义,起的不好,忽略,反正这些类就是负责网络层工作的)


主要看JHDataProxy这个类,其它的类就是将接口按业务大类划分到不同的类别(Category)文件中去实现,避免JHDataProxy文件过于臃肿

@interface JHDataProxy : NSObject{    AFHTTPSessionManager *_manager;//公用一个AFHTTPSessionManager,复用http连接        AFHTTPSessionManager *_fileServerManager;        AFHTTPSessionManager *_imManager;}-(void)cancleAllRequest;-(void)setSystemParams:(NSMutableDictionary *)paramDict;-(BOOL)responseIsSuccess:(id)responseObject;
-(id)init{    self = [super init];    if (self)    {        _manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:ServerAdd]];        _manager.requestSerializer = [AFJSONRequestSerializer serializer];        _manager.requestSerializer.timeoutInterval = 15;        [_manager.requestSerializer setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];                AFJSONResponseSerializer *responseSerializer = [AFJSONResponseSerializer serializer];        responseSerializer.removesKeysWithNullValues = YES;        responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain",@"application/json", nil];        _manager.responseSerializer = responseSerializer;        .........        //_fileServerManager 配置                //_imManager 配置                ............    }    return self;}-(void)cancleAllRequest{    //取消所有的请求}//统一设置一些系统级参数-(void)setSystemParams:(NSMutableDictionary *)paramDict{    [paramDict setObject:APP_CLIENT_ID forKey:@"clientId"];    [paramDict setObject:[JHPhoneManager shareJHPhoneManager].appUser.workId forKey:@"userName"];    NSInteger currentTime = [[NSDate date] timeIntervalSince1970];    [paramDict setObject:[NSNumber numberWithInteger:currentTime] forKey:@"timestamp"];        [paramDict setObject:[NSNumber numberWithInteger:[JHPhoneManager shareJHPhoneManager].appUser.companyId] forKey:@"companyId"];    .............        return;}//回调里公共参数的统一处理-(BOOL)responseIsSuccess:(id)responseObject{    if (responseObject)    {        int errorCode = [[responseObject objectForKey:@"errorCode"] intValue];        if (errorCode == 0)        {            return YES;        }        else        {            if (errorCode == 4001 || errorCode == 4002)            {                [[JHPhoneManager shareJHPhoneManager] clearAllForLogout];                 .............            }            return NO;        }    }    else        return NO;}
通过JHDataProxy类的实现,我可以达到以下目的,1.网络(连接)资源的共享复用,多个请求可以共用一个HttpSessionManager对象(好处可以看看关于Afnetworking的使用及底层原理,其它框架也有类似机制);2.对HttpSessionManager的统一配置,服务器baseurl、cache策略、超时时间、数据请求返回格式的设置等等,都统一、集中到了JHDataProxy类中;3.请求控制的集中管理,比如统一的cancleRequest操作等;4.通用基础参数的控制,setSystemParams集中控制了系统级参数的统一添加;5.接口返回的统一处理,比如处理所有接口的response errorCode、解析返回中的sessionId等等。这些基础性的通用设置、操作和处理,都放到了一处集中处理,不用分散到每次具体的请求与回调当中,达到了项目内网络层的重用,同时便用管理和设置。

2.设计改进

之前提到了,连接层我们可以使用http,socket,亦或是别的;同时还存在ASIHttpRequest、AFNetworking、GCDAsyncSocket很多不同的库供我们使用,上面的设计我直接在JHProxy类中对AFNetworking进行了调用,网络业务层与连接层直接缠绕在了一起,没有做到连接层的分离,如果以后项目中要替换不同的第三方库或是更换不同的连接方式,那么就会对JHProxy造成很大的破坏,为了日后代码的安全性、扩展性及降低维护成本,我们可以单独把连接层提出来。如我在另一个项目中的设计,(同样,名字起的不好......)

FEIM_Network即是对连接层的抽离,CUHDataProxy则是网络业务层的实现,CUHDataProxy会对FEIM_Network进行调用,比如CUHDataProxy中的这个实现

-(void)getDiseaseList:(NSInteger)departmentId{    [_netWork getDiseaseList:departmentId];//_netWork是FEIM_NetWork对象}
它完全不用关心FEIM_Network是用什么方式与服务器打交道的,调用FEIM_Network的getDisraseList方法后,它只要实现FEIM_Network的请求回调,处理回调即可了,FEIM_Network可能用的是ASIHttpRequest也可能是AFNetWorking,甚至也可能换成Socket,都无所谓,连接层的修改与实现都在FEIM_Network中去做就行了,不会对CUHDataProxy造成任何影响。(PS:FEIM_Network还可以进一步通过设计做到与业务无关,不过目前我还没有在项目中做到过这个层次的进一步分离,没有这个强需求).

二.尽力而为--回调数据的处理

刚开始写代码时,比如A界面(控制器)我会发出a请求,我就会把请求及回调处理都写在A界面里;如果B界面(控制器)也发出a请求,同样我又会把请求及回调处理又都copy到B界面里...当然,copy当然不麻烦,但要是a请求的请求参数或回调等任何一处有了修改,那我可就不能忘了要修改两处(A、B里都要改好).这样维护起来可就麻烦了,而且同样的代码这里拷贝一份、那里拷贝一份,也实在是不雅观,同时还会大大造成控制层的臃肿,让上层承担了过多的任务。
其实本质上,上层对数据的请求(不管是网络还是本地数据请求),如何请求,请求返回后的数据处理它都是不关心的,它就想拿到最终数据(模型)结果直接使用(它更关心的应该是数据的呈现及业务间的传递).同时,其实针对每一个请求,请求完成后的后续业务处理逻辑绝大部分情况下都是一样的,所以网络层进行封装处理,主动承担业务、数据处理任务,是顺理成章的。举几个例子,

示例一、获取离线消息接口
-(void)getOfflineMsg:(void(^)())success fail:(void(^)())fail{    NSMutableDictionary *paramDict = [[NSMutableDictionary alloc] init];    [self setSystemParams:paramDict];        double lastedReceiveMsgTime = [JHBaseMessage getLastedReceiveMsgTime];    [paramDict setObject:[NSNumber numberWithInteger:lastedReceiveMsgTime*1000] forKey:@"lastMessageTime"];        [_imManager POST:GetOfflineMsg parameters:paramDict progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {        if ([self responseIsSuccess:responseObject])        {            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{                //群离线消息                ...........                                //个人离线消息                .......                                //离线serverMsg消息                .........                }];                                //保存离线消息,排重,计算未读消息数目,生成最近消息对象,按人(群)生成排重后的离线消息数组                [JHBaseMessage saveOfflineMsgs:allSingleMsgs roomMsgs:allRoomsMsgs result:^(NSMutableArray *recentMsgs, NSMutableDictionary *distinctMsgsDict) {                    [JHRecentMessageRecord saveOrUpdateRecentMsgs:recentMsgs needForceSetUnReadNum:NO];                    ...............                    dispatch_async(dispatch_get_main_queue(), ^{                        ...........                        success();                    });                }];            });        }        else        {            .........            fail();        }    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {        if(error.code == -999)            return;        else        {            .........            fail();        }    }];}
离线消息取得后,我需要解析出群离线消息、个人离线消息、服务器离线消息、对消息进行排重、计算未读消息数、消息本地存储等等,这一系列操作都是统一的,无论我在何时何处去获取离线消息都要进行这些处理,那我何不把这些处理都封装到网络层中呢,这样上层任何地方只要简简单单的调用[_dataProxy getOfflineMsg]一句话后,后续一系列的数据业务处理、数据存储等等它都不用去操心了,最终它只要在回调里完成它上层界面反馈等上层业务即可。

示例二、获取用户详情接口
-(void)getBasicUserInfo:(NSString *)rqUserId success:(void(^)(JHCorpContact *contact))success fail:(void(^)())fail{    NSMutableDictionary *paramDict = [[NSMutableDictionary alloc] init];    [self setSystemParams:paramDict];    [paramDict setObject:rqUserId forKey:@"rq_userName"];        [_manager POST:GetBasicUserInfo parameters:paramDict progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {        if ([self responseIsSuccess:responseObject])        {            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{                //数据解析                NSDictionary *userDict = [responseObject objectForKey:@"lightUser"];                JHCorpContact *contact = [[JHCorpContact alloc] init];                contact.contactType = CORP_CONTACT;                contact.contactId = [userDict objectForKey:@"userName"];                contact.name = [userDict objectForKey:@"name"];                contact.sex = [[userDict objectForKey:@"gender"] intValue];                .........                //数据存储                [JHCorpContact saveOrUpdateCorpContactBasicInfo:contact];                                //返回模型                dispatch_async(dispatch_get_main_queue(), ^{                    success(contact);                });            });        }        else        {            fail();        }    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {        if(error.code == -999)            return;        else            fail();    }];}
同样,无论何时何处我调用获取用户详情接口,我要做的都是解析服务器返回的JSON数据转化成用户模型、把用户的详情更新到本地存储,这些处理都是一致的。上层调用时拿到的就是一个返回的数据模型,它唯一要关心的就是这个模型在上层展示、反馈或交由下一个业务即可。

示例三、消息发送
- (void)sendMsgTo:(NSString *)friendUserId content:(NSString *)msgContent finish:(void(^)(JHMessageObject *msg))finish{    //创建消息相关模型    JHMessageObject *msg = [[JHMessageObject alloc] init];    msg.msgDate = [self getCurrentTime];    msg.msgUniqueID = [JH_Util uniqueString];    msg.msgContent = msgContent;    .......    .........        //创建xmpp消息    XMPPMessage *mes=[XMPPMessage messageWithType:@"chat" to:[XMPPJID jidWithUser:friendUserId domain:JHIM_DOMAIN resource:IMSOURCE] elementID:msg.msgUniqueID];    [mes addChild:[DDXMLNode elementWithName:@"body" stringValue:msgContent]];    [mes addChild:[self buildTextMsgExtendField:msg]];        //发送消息    if ([self.xmppStream isAuthenticated]) {        msg.msgStatus = MsgSending;        //发送消息        [self sendXMPPMessage:mes];    }    else    {        msg.msgStatus = MsgFail;    }        //消息存储    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{        [JHMessageObject saveSingleMsg:msg];        [JHRecentMessageRecord saveOrUpdateRecentMsg:recentMsg needForceSetUnReadNum:YES];    });        finish(msg);}
  举第三个例子,想要说明的是这种设计的另一个好处。这里利用xmppstream与openfire服务器打交道(有来有回,其实就是socket),同样,无论何时何处去做消息发送,都是这一套业务,建立模型->发送->存储模型->回调(返回模型),这种统一的业务处理,都应按照上面的设计思路封装起来,上层只需要单纯的关心何时去触发发送动作,动作完成后如何反馈或进行下一步业务即可。前不久,公司准备把我们的IM基础通讯部分单独提出来,封装成SDK,按照这种设计实现的代码,我在做SDK接口封装时,基本上没有做任何额外的工作,直接把网络层、数据层文件从项目中拿出来后,简单整理修改了一下,SDK就出来了(我看最终的SDK和环信的接口定义、设计思路基本都是一致的,SDK负责的就是收发消息(网络层)+本地消息存取(数据层)的工作),通过设计可以很好的将下层(数据层+网络层)与上层(应用层)进行分离,下层代码具有高度的独立性与可(复)用性。

  所以,网络层虽然名字里只有网络两个字,但它不应狭隘的仅仅关心网络本身的连接相关的工作,尽力而为,把数据取过来后,后面的一系列统一的业务处理,能做的就尽量都做了,这样将大大减轻上层的负担,同时做到对上层透明(上层随便变化、修改,甚至给第三方开发者去重新独立开发,都无所谓,我的底层业务都是统一固定的,并且是封装好的,下层对上层完全没有依赖,上层对下层来说就是调用者,谁调用,怎么调用都是无所谓的)。
  
  某种意义上来讲,如果完成了下层(数据层+网络层)的开发工作后,整个项目已经就具有了业务完备性,其实项目已经"完成"了,好比制造汽车,汽车的所有基础部件已经都制造好了(已经能跑了),剩下的工作就是给汽车加上一个壳子(UI界面),提供驾驶者驾驶汽车的入口(安上方向盘、挡把、刹车踏板、油门踏板等等这些)(交互与业务触发、串联),而这些就是上层所承担的任务,下一篇写上层(应用层)设计

0 0
原创粉丝点击