IOS网络请求的简单封装设计

做ios项目已经几年了,最初是借用别人的框架,然后再框架上去修改。后来自己项目开始自己学着去写框架结构,不管是用什么样的框架,里面网络层的设计都是比较固定的,所以针对网络层的部分,我单独把这个地方进行了重构,整理成了现在的模样。现在把我的思路整理一下,希望大家如果有什么好的意见或者建议,可以提出来。共同进步。

1、整体思路

整体的思路也是沿用MVC的结构。将两部分内容进行的独立的封装:第一个是网络数据请求,第二个是对网络请求的调用和响应处理部分。而其中的C层,也就是controller部分,只需要处理最后响应回来的数据,进行View的操作即可。
同时因为在项目中考虑到了现在3种数据类型,定义了XML、JSON和ProtocolBuffer。


思路图

上面的图比较简单,只是为了表达一个思路,以及过程。

2、网络请求:NetworkRequest

这部分用的实现是AFNetworking,定义了一个单例的client,封装了client对于网络请求的调用。
先定义一个配置文件NetworkConfig和一个数据封装类NetManager。

// NetworkConfig.h
typedef NS_ENUM(NSInteger, NetErrorCode) {
    NetErrorCodeEncryption = 1,//加密错误
    NetErrorCodeDecryption = 2,//解密错误
    NetErrorCode404 = 404,
    NetErrorCode500 = 500,
    NetErrorCodeTimeOut = 3,
    NetErrorCodeDictionaryModel = 4,//字典数据转换为model数据时错误
    NetErrorCodeOther = 5
};

typedef NS_ENUM(NSInteger, NetType) {
    XML = 0,
    JSON = 1,
    PROTOBUF = 2,
};

typedef void (^ReturnValueBlock) (id returnValue);
typedef void (^ErrorCodeBlock) (NetErrorCode errorCode);

NetworkConfig.h文件中,定义了网络异常的类型、网络请求协议的类型,以及网络请求中的block块。在网络请求部分,我是用了block来处理网络请求的响应和异常返回的。

//NetManager.h
@interface NetManager : NSObject
/**
 *  原始数据
 *  json和xml的返回类型为NSDictionary
 *  protocolBuffer则是NSData类型
 */
@property (nonatomic,strong) id rawData;
@property (nonatomic,assign) NetType type;//返回的数据类型
@end

NetManager的.m文件没有做具体的实现。这个类只是封装了网络请求的响应数据以及数据类型。这里的数据都是原始数据,方便后面中间层对数据的解析处理。
准备工作已经做好了,那么下面就是创建具体的网络请求部分了。先是单例模式的请求client。

//NetHttpRequestClient.h
@interface NetHttpRequestClient : AFHTTPSessionManager
+ (instancetype)sharedClient;
@end

//NetHttpRequestClient.m
@implementation NetHttpRequestClient

+ (instancetype)sharedClient {
    static NetHttpRequestClient *_sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedClient = [NetHttpRequestClient manager];
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
        [securityPolicy setValidatesDomainName:NO];
        _sharedClient.securityPolicy = securityPolicy;
        _sharedClient.responseSerializer = [AFHTTPResponseSerializer serializer];
        _sharedClient.requestSerializer.timeoutInterval = 40.0f;
    });
    return _sharedClient;
}
@end

client创建好了,就是网络请求的实现部分了。

#define SERVER_TYPE_XML @"xml/"
#define SERVER_TYPE_JSON @"json/"
#define SERVER_TYPE_PROTOBUF @"protobuf/"

@interface NetRequestClass : NSObject


#pragma 监测网络的可链接性
+ (BOOL) netWorkReachabilityWithURLString:(NSString *) strUrl;

#pragma POST请求
/**
 *requestURLString:请求地址,不是全地址,由security+类型+方法名组合而成
 *parameter:参数,参数中需要包含方法名的参数
 */
+ (NSURLSessionDataTask *) NetRequestPOSTWithRequestMethod: (NSString *) methodName
                                             WithParameter: (NSDictionary *) parameter
                                           WithRequestType:(NetType)type
                                      WithReturnValeuBlock: (ReturnValueBlock) block
                                        WithErrorCodeBlock: (ErrorCodeBlock) errorBlock;

#pragma protocol_buffer 的请求
/**
 *  protocol_buffer 的请求
 *
 *  @param methodName 方法名
 *  @param data       protocol_buffer请求的requestData数据
 *
 */
+ (NSURLSessionDataTask *) NetProtocolBufferRequestPOSTWithRequestMethod: (NSString *) methodName
                                                                WithData: (NSData *) data
                                                    WithReturnValeuBlock: (ReturnValueBlock) block
                                                      WithErrorCodeBlock: (ErrorCodeBlock) errorBlock;

+(NSMutableDictionary *)addBasicParamters:(NSDictionary *)paramters;
@end

.h文件中,定义了三种类型,同时,将xml/json和protocolBuffer的请求分离开,因为在里面具体的实现部分有一点细微区别,所以没有将三者和在一起处理,而是分离开,方便后期维护。

//NetRequestClass.m
@implementation NetRequestClass
#pragma 监测网络的可链接性
+ (BOOL) netWorkReachabilityWithURLString:(NSString *) strUrl{
    __block BOOL netState = NO;
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    NSOperationQueue *operationQueue = manager.operationQueue;
    [manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        switch (status) {
            case AFNetworkReachabilityStatusReachableViaWWAN:
            case AFNetworkReachabilityStatusReachableViaWiFi:
                [operationQueue setSuspended:NO];
                netState = YES;
                break;
            case AFNetworkReachabilityStatusNotReachable:
                netState = NO;
            default:
                [operationQueue setSuspended:YES];
                break;
        }
    }];
    [manager.reachabilityManager startMonitoring];
    return netState;
}

#pragma --mark POST请求方式
+(NSURLSessionDataTask *)NetRequestPOSTWithRequestMethod:(NSString *) methodName
                                           WithParameter:(NSDictionary *) parameter
                                         WithRequestType:(NetType)type
                                    WithReturnValeuBlock:(ReturnValueBlock) block
                                      WithErrorCodeBlock:(ErrorCodeBlock) errorBlock{

    NetHttpRequestClient *client = [NetHttpRequestClient sharedClient];
    client.requestSerializer =  [AFHTTPRequestSerializer serializer];
    NSError *theError            = nil;
    NSString *requestContent     = nil;
    NSDictionary *allParameters  = [self addBasicParamters:parameter];
    [allParameters setValue:methodName forKey:@"name"];
    switch (type) {
        case XML:{//将参数数据转换为xml字符串
            NSDictionary *requestDic = [NSDictionary dictionaryWithObject:[self formateXMLParameters:allParameters] forKey:@"methodRequest"];
            requestContent           = [requestDic innerXML];
        }
            break;
        case JSON:{//将参数数据转换为jsonData
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:allParameters options:NSJSONWritingPrettyPrinted error:&theError];
            requestContent   = [[NSString alloc] initWithData:jsonData  encoding:NSUTF8StringEncoding];
        }
            break;
        default:
            break;
    }
    NSString *encryptXml = @"加密后的字符串";
    NSString *serverIpUrl = @"服务器地址";
    NSDictionary *encryDic = [[NSDictionary alloc]initWithObjects:@[[NSNull null]] forKeys:@[encryptXml]];
    NSString *requestUrl = [NSString stringWithFormat:@"%@%@%@",serverIpUrl,type == JSON?SERVER_TYPE_JSON:SERVER_TYPE_XML,methodName];
    //启动请求
    NSURLSessionDataTask *dataTask = [client POST:requestUrl parameters:encryDic progress:nil success:^(NSURLSessionDataTask *dataTask, id responseObject) {
        if(dataTask){
            [dataTask cancel];
            dataTask = nil;
        }
        //获取得到的数据
        NSString *theResponse = [[NSString alloc] initWithBytes:[responseObject bytes] length:[responseObject length] encoding:NSUTF8StringEncoding];
        NSString *decryptStr = nil;
        if(theResponse){
            decryptStr = @"解密响应数据";
        }
        NetManager *manager   = [[NetManager alloc]init];
        NSDictionary *dataDoc = nil;
        if(type == XML){
            dataDoc = [NSDictionary dictionaryWithXMLString:decryptStr];
            manager.type = XML;
        }else if(type == JSON){
            dataDoc = [decryptStr objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode];
            manager.type = JSON;
        }
        manager.rawData = dataDoc;
        block(manager);
    } failure:^(NSURLSessionDataTask *dataTask, NSError *error) {
        if(dataTask){
            [dataTask cancel];
            dataTask = nil;
        }
        NSInteger code = error.code;
        if(code == NSURLErrorCancelled){//取消
            return;
        }else if(code == NSURLErrorTimedOut){//超时
            errorBlock(NetErrorCodeTimeOut);
            return;
        }
        NSDictionary *userInfo      = error.userInfo;//NSURLErrorCancelled
        NSHTTPURLResponse *response = userInfo[AFNetworkingOperationFailingURLResponseErrorKey];
        if(response.statusCode == 404){
            errorBlock(NetErrorCode404);
        }else if(response.statusCode > 500){
            errorBlock(NetErrorCode500);
        }else{
            errorBlock(NetErrorCodeOther);
        }
    }];
    return dataTask;
}

+(NSMutableDictionary *)formateXMLParameters:(NSDictionary *)paramters{
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:paramters];
    for(NSString *key in  paramters.allKeys){
        NSObject *value = paramters[key];
        [params removeObjectForKey:key];
        if([value isKindOfClass:[NSString class]]){
            [params setObject:value forKey:[NSString stringWithFormat:@"_%@",key]];
        }else if([value isKindOfClass:[NSDictionary class]]){
            NSDictionary *cp = [self formateXMLParameters:(NSDictionary *)value];
            [params setObject:cp forKey:[NSString stringWithFormat:@"%@",key]];
        }else if([value isKindOfClass:[NSArray class]]){
            NSMutableArray *array =  [NSMutableArray new];
            for(NSDictionary *dic in (NSArray *)value){
                NSDictionary *cp = [self formateXMLParameters:dic];
                [array addObject:cp];
            }
            [params setObject:array forKey:[NSString stringWithFormat:@"%@",key]];
        }
    }
    return params;
}

+(NSMutableDictionary *)addBasicParamters:(NSDictionary *)paramters{
    NSMutableDictionary *params = nil;
    if(paramters){
        params = [[NSMutableDictionary alloc] initWithDictionary:paramters];
    }else{
        params = [[NSMutableDictionary alloc] init];
    }
    /***
     为请求添加一些默认的基本信息,比如版本号,用户id等等。
     **/
    return params;
}

+(NSURLSessionDataTask *)NetProtocolBufferRequestPOSTWithRequestMethod:(NSString *)methodName WithData:(NSData *)data WithReturnValeuBlock:(ReturnValueBlock)block WithErrorCodeBlock:(ErrorCodeBlock)errorBlock{

    NetHttpRequestClient *client = [NetHttpRequestClient sharedClient];
    client.requestSerializer =  [AFProtoBufRequestSerializer serializer];
    NSDictionary *encryDic       = [[NSDictionary alloc]initWithObjects:@[[NSNull null]] forKeys:@[data]];
    NSString *serverIpUrl = @"服务器地址";
    NSString *requestUrl = [NSString stringWithFormat:@"%@%@%@",serverIpUrl,SERVER_TYPE_PROTOBUF,methodName];
    //启动请求
    NSURLSessionDataTask *dataTask = [client POST:requestUrl parameters:encryDic progress:nil success:^(NSURLSessionDataTask *dataTask, id responseObject) {
        if(dataTask){
            [dataTask cancel];
            dataTask = nil;
        }
        //获取得到的数据
        NetManager *manager = [[NetManager alloc]init];
        manager.rawData     = responseObject;
        manager.type        = PROTOBUF;
        block(manager);
    } failure:^(NSURLSessionDataTask *dataTask, NSError *error) {
        if(dataTask){
            [dataTask cancel];
            dataTask = nil;
        }
        NSInteger code = error.code;
        if(code == NSURLErrorCancelled){//取消
            return;
        }else if(code == NSURLErrorTimedOut){//超时
            if(errorBlock){
                errorBlock(NetErrorCodeTimeOut);
            }
            return;
        }
        NSDictionary *userInfo = error.userInfo;//NSURLErrorCancelled
        NSHTTPURLResponse *response = userInfo[AFNetworkingOperationFailingURLResponseErrorKey];
        if(errorBlock){
            if(response.statusCode == 404){
                errorBlock(NetErrorCode404);
            }else if(response.statusCode > 500){
                errorBlock(NetErrorCode500);
            }else{
                errorBlock(NetErrorCodeOther);
            }
        }
    }];
    return dataTask;
}

上面的代码已经很完整了,就不用过多的解释。这是大家能够看到NetManager的作用了,在block的传递中,用的NetManager数据。

3、中间层NetworkModel

这层的主要作用是衔接ViewController与NetworkRequest两层,减少两层的耦合,封装处理网络请求返回的数据。
我们先定义了一个基类和一个协议。基类的作用是让所有实现网络数据处理的类都继承予它,而协议是为了衔接ViewController和NetworkModel之间,为网络数据的传递。

@protocol NetworkModelDelegate ;
@interface NetworkModelClass : NSObject

@property(nonatomic, weak, nullable) id<NetworkModelDelegate> delegate;

@end

@protocol NetworkModelDelegate <NSObject>

@optional
//用于xml或者json数据返回
- (void)networkModelWithNetResponse:(NSDictionary * _Nullable)response withMethodName:(NSString *)methodName;
- (void)networkModelWithNetError:(NetErrorCode)errorCode withMethodName:(NSString *)methodName;
//用于Protobuffer数据
- (void)networkModelWithNetDataResponse:(NSData * _Nullable)response withMethodName:(NSString *)methodName;

@end

这个类很简单,不需要过多的解释。

4、例子

好了,接下来举一个简单的使用例子,就可以明白是怎么工作的了。
创建了一个TestNetworkModel,其继承了NetworkModelClass,在里面,我们创建了两个对外的方法,为了方便,特地提供了一个关于protocolbuffer的使用。

//TestNetworkModel.h
@interface TestNetworkModel : NetworkModelClass
-(void)queryDataWithXML:(NSString *)para;
-(void)queryDataWithProtocolBuffer:(NSString *)para;
@end
//TestNetworkModel.m
@implementation TestNetworkModel{
    NSURLSessionDataTask *_task1;
    NSURLSessionDataTask *_task2;
}


-(void)queryDataWithXML:(NSString *)para{
    if(_task1 && (_task1.state == NSURLSessionTaskStateRunning || _task1.state == NSURLSessionTaskStateSuspended)){
        [_task1 cancel];
        _task1 = nil;
    }
    NSDictionary *parameter = nil;
    if(para){
        parameter = [NSDictionary dictionaryWithObjectsAndKeys:para,@"key_para",nil];
    }
    __block typeof(self) weakSelf = self;
    _task1 = [NetRequestClass NetRequestPOSTWithRequestMethod:@"方法名" WithParameter:parameter WithRequestType:XML WithReturnValeuBlock:^(id netManger){
        [weakSelf anlyzeDataWithXML:((NetManager *)netManger).rawData];
    } WithErrorCodeBlock:^(NetErrorCode errorCode){
        [weakSelf networkError:@"方法名" withErrorCode:errorCode];
    }];
}

-(void)anlyzeDataWithXML:(NSDictionary *)data{
    /**
     此处省略数据解析过程
     */
    NSMutableDictionary *dataDic = nil;
    NSString *methodName = nil;
    if(self.delegate){
        [self.delegate networkModelWithNetResponse:dataDic withMethodName:methodName];
    }
}


-(void)queryDataWithProtocolBuffer:(NSString *)para{

    if(_task2 && (_task2.state == NSURLSessionTaskStateRunning || _task2.state == NSURLSessionTaskStateSuspended)){
        [_task2 cancel];
        _task2 = nil;
    }

    NSData *data = nil;//封装的protocolbuffer的请求数据
    __block typeof(self) weakSelf = self;
    _task2 = [NetRequestClass NetProtocolBufferRequestPOSTWithRequestMethod:@"方法名" WithData:data WithReturnValeuBlock:^(id resp){
        if (resp && [resp isKindOfClass:[NetManager class]]) {
            NetManager *respData = (NetManager *)resp;
            if(weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(networkModelWithNetDataResponse:withMethodName:)]){
                [weakSelf.delegate networkModelWithNetDataResponse:respData.rawData withMethodName:@"方法名"];
            }
        }

    } WithErrorCodeBlock:^(NetErrorCode errorCode){
        [weakSelf networkError:@"方法名" withErrorCode:errorCode];
    }];
}

-(void)networkError:(NSString *)methodName withErrorCode:(NetErrorCode)errorCode{
    if(self.delegate){
        [self.delegate networkModelWithNetError:errorCode withMethodName:methodName];
    }
}
@end

上面的.m文件已经很明白了,通过delegate将网络请求的数据传递出去,而不需要关心delegate的实现者是谁。同时,此类中,对于网络数据的处理和解析,可以独自完成,以实现最大程度的独立封装。

接下来就是如何在ViewController中使用了。

//ViewController.h
@interface ViewController : UIViewController<NetworkModelDelegate>{
    TestNetworkModel *_networkModel;
}
@end

//ViewController.m
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    _networkModel = [[TestNetworkModel alloc]init];
    _networkModel.delegate = self;

    [_networkModel queryDataWithXML:@"方法名1"];
    [_networkModel queryDataWithProtocolBuffer:@"方法名2"];
}

#pragma mark - 网络请求的返回s
- (void)networkModelWithNetDataResponse:(NSData *)response withMethodName:(NSString *)methodName{

}

- (void)networkModelWithNetResponse:(NSDictionary *)response withMethodName:(NSString *)methodName{

}

- (void)networkModelWithNetError:(NetErrorCode)errorCode withMethodName:(NSString *)methodName{

}
@end

上面已经很清楚了,ViewController通过对TestNetworkModel的实例来发起网络请求,网络请求的数据通过TestNetworkModel中的delegate,也就是ViewController来处理网路请求解析后的数据,以供View的处理和显示。

至此,整个流程已经完整了,写的比较简单,只为了告诉大家这个实现的过程而已。其实,如果不想数据这么直接暴露给ViewController,实际可以在ViewController和NetworkModel直接再创建一层对数据的加工和逻辑处理层,而只暴露给ViewController需要显示的东西。
其实用NetworkModel的思路,也是为了方便网络请求的重用,同一个请求可能再多个地方调用,这样只需要在不同的地方创建不同的NetworkModel实例就可以了,最大程度的减少耦合。

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