一劳永逸的解决AFNetworking3.0网络请求问题(面向对象封装大法,block回调)

AFNetworking在iOS网络请求第三方库中占据着半壁*,前段时间将AFNetworking进行了3.0版本的迁移,运用面向对象的设计将代码进行封装整合,这篇文章主要为还在寻找AFNetworking集成代码或者准备3.0迁移的各位童鞋们提供思路,同时自定义了字典模型转换方法,需要的朋友也可以作为参考,还望各位老司机批评指正!先上代码框架图:

一劳永逸的解决AFNetworking3.0网络请求问题(面向对象封装大法,block回调)

1、DB数据访问层,在AFNetworkingManager中我将AFNetworking的GET/POST/DELETE/PUT方法封装,提供了以下接口:

 /**
* get方式请求数据
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param class 返回数据模型类
* @param block block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
-(void)getDataFromUrl:(NSString *)strUrl
headers:(NSDictionary *)headers
params:(NSDictionary *)params
class:(Class)class
block:(CompletionLoad)block
blockError:(void (^)(JsonCommonResultBase *))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; /**
* post方式更新数据
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param class 返回数据模型类
* @param block block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)postDataFromUrl:(NSString*)strUrl
headers:(NSDictionary*)headers
params:(NSDictionary*)params
class:(Class)class
block:(CompletionLoad)block
blockError:(void(^)(JsonCommonResultBase *))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; /**
* put方式更新数据
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param class 返回数据模型类
* @param block block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)putDataFromUrl:(NSString*)strUrl
headers:(NSDictionary*)headers
params:(NSDictionary*)params
class:(Class)class
block:(CompletionLoad)block
blockError:(void(^)(id))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; /**
* delete方式删除数据
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param class 返回数据模型类
* @param block block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)deleteDataFromUrl:(NSString*)strUrl
headers:(NSDictionary*)headers
params:(NSDictionary*)params
class:(Class)class
block:(CompletionLoad)block
blockError:(void(^)(JsonCommonResultBase *))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; /**
* post方式更新数据(上传文件如图片)
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param dataFiles 文件数据
* @param class 返回数据模型类
* @param block block结果回调
* @param progressBlock block进度回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)uploadDataFromUrl:(NSString *)strUrl
headers:(NSDictionary *)headers
params:(NSDictionary *)params
dataFiles:(NSArray *)dataFiles
progressBlock:(LoadProgress)progressBlock
block:(CompletionLoad)block
class:(Class)class
blockError:(void (^)(JsonCommonResultBase *))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut;

AFNetworking封装

针对AFNetworking底层封装AFNetworkingManager后,是不是就可以直接在Service调用GET/POST/DELETE/PUT接口访问数据了呢?理论上是完全可以的,但是我们在实际开发中往往还需要自定义或者个性化一些效果如菊花等待框、阴影效果,提示文案等,所以本人建议在AFNetworkingManager基础上再包装一层专门用于Service对接,这样的好处是Service层完全不必关心AFNetworking的封装实现和序列化、授权等等问题,这样也便于后续的维护与版本的升级,好了我们再看看对接Service的ZTHttpManager:

/**
* get方式请求数据(内部封装菊花等待框)
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param parentView 菊花等待框寄托视图
* @param showShadow 是否阴影父视图
* @param blockRtn block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)getDataToUrl:(NSString*)strUrl
headers:(NSDictionary*)headers
params:(NSDictionary*)params
parentView:(UIView*)parentView
showShadow:(BOOL)showShadow
class:(Class)class
blockRtn:(void (^)(id ))blockRtn
blockError:(void(^)(JsonCommonResultBase*))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; /**
* post方式提交数据(内部封装菊花等待框)
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param parentView 菊花等待框寄托父视图
* @param showShadow 是否阴影父视图
* @param blockRtn block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)postDataToUrl:(NSString *)strUrl
headers:(NSDictionary *)headers
params:(NSDictionary *)params
parentView:(UIView *)parentView
showShadow:(BOOL)showShadow
class:(Class)class
blockRtn:(void (^)(id))blockRtn
blockError:(void(^)(JsonCommonResultBase*))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; /**
* delete方式删除数据(内部封装菊花等待框)
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param parentView 菊花等待框寄托视图
* @param showShadow 是否阴影父视图
* @param blockRtn block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)deleteDataToUrl:(NSString*)strUrl
headers:(NSDictionary*)headers
params:(NSDictionary*)params
parentView:(UIView*)parentView
showShadow:(BOOL)showShadow
class:(Class)class
blockRtn:(void (^)(id ))blockRtn
blockError:(void(^)(JsonCommonResultBase*))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; /**
* put方式提交数据(内部封装菊花等待框)
*
* @param strUrl api地址
* @param headers 头部信息
* @param params 可变参数信息
* @param parentView 菊花等待框寄托视图
* @param showShadow 是否阴影父视图
* @param blockRtn block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)putDataToUrl:(NSString*)strUrl
headers:(NSDictionary*)headers
params:(NSDictionary*)params
parentView:(UIView*)parentView
showShadow:(BOOL)showShadow
class:(Class)class
blockRtn:(void (^)(id))blockRtn
blockError:(void(^)(JsonCommonResultBase*))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; /**
* post方式上传文件
* @param strUrl api地址
* @param parentView 菊花等待框寄托视图
* @param showShadow 是否阴影父视图
* @param blockRtn block结果回调
* @param blockError block错误回调
* @param blockTimeOut block超时回调
*/
- (void)uploadImgFromUrl:(NSString*)strUrl
fileItems:(NSArray*)fileItems
parentView:(UIView*)parentView
showShadow:(BOOL)showShadow
headers:(NSDictionary *)headers
params:(NSDictionary *)params
class:(Class)class
blockProgress:(void (^)(NSString *))blockProgress
blockRtn:(void (^)(id))blockRtn
blockError:(void(^)(JsonCommonResultBase*))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut;

ZTHttpManager封装

好了,在这里完成了DB层的代码,访问API就毫无压力了!

2、模型基类JsonCommonResultBase/SerializationBaseModel

在这里我要说明一点,这里的模型基类是按照我们公司后台返回的API格式自定义,不一定适合每个人,但是可以作为各位的参考,具体的API返回数据结构为:

{
page = {
hasMore = ;
totalRows = ;
};
result = (
{
annexInfoStatus = Pending;
bsBeginTime = "2017-03-16 00:00:00";
bsEndTime = "2018-03-15 23:59:59";
bzBeginTime = "2017-03-16 00:00:00";
bzEndTime = "2018-03-15 23:59:59";
canRenewal = ;
company = {
code = alltrust;
id = ;
};
totalPremiums = "9632.09";
totalPremiumsText = "\U00a59,632.09";
verifyStatus = Verified;
}
);
status = ;
}

API返回200数据格式

针对上述的数据格式,自定义的模型基类如下(BTW这里多层级的数据转化也是毫无压力的,完全OK):

@interface SerializationBaseModel : NSObject<NSCopying>

// 获取列表字典
- (NSDictionary *)objectClassInArray; @end @implementation SerializationBaseModel - (id)copyWithZone:(NSZone *)zone{
return (id)self;
} // 获取列表字典 (具体result实现在子类中)
- (NSDictionary *)objectClassInArray{
return nil;
} @end

序列化model基类 需要继承NSCopying

#pragma mark - 分页数据模型
@interface ApiPage : SerializationBaseModel /**
* 总行数
*/
@property (nonatomic,assign) NSInteger totalRows; /**
* 是否还有数据
*/
@property (nonatomic,assign) BOOL hasMore; @end #pragma mark - Json数据模型
@interface JsonCommonResultBase : SerializationBaseModel /**
* 错误编码
*/
@property (nonatomic,copy) NSString *errCode; /**
* 错误消息
*/
@property (nonatomic,copy) NSString *errMsg; /**
* 请求状态
*/
@property (nonatomic,assign) NSInteger status; /**
* 分页信息
*/
@property (nonatomic,strong) ApiPage *page; @end //-------------------------线上是基类,线下是子类--------------------------------- #import "JsonCommonResultBase.h" @interface ZTTestModel : SerializationBaseModel @property (nonatomic,assign) NSInteger stuId; @property (nonatomic,copy) NSString *stuName; @property (nonatomic,copy) NSString *stuClassName; @property (nonatomic,copy) NSString *stuScore; @end @interface ZTTestModelResult : JsonCommonResultBase // BTW 实现多层级嵌套或者单数据模型也是没有问题的,可参照上面代码“api返回200数据格式”,这里定义好就行了 @property (nonatomic,strong) NSMutableArray<ZTTestModel*> *result; @end // 重点来了,对于列表格式的result使用NSMutableArray<ZTTestModel*> *result类似定义后就搞定了吗?那你就想太多了,我们还需要再实现代码中添加字典转化代码,如下:
#import "ZTTestModelResult.h" @implementation ZTTestModel @end @implementation ZTTestModelResult // 拿出小本本记好笔记,针对列表格式的result必须添加这段代码,单对象数据不需要
- (NSDictionary *)objectClassInArray{
return @{@"result" : [ZTTestModel class]};
} @end

返回数据基类与具体实现子类

 
那么重点来了,我们知道AFNetworking调用API后返回的数据格式流为:NSData -> NSDictionary ,我们需要先将responseObject数据从NSData转化为NSDictionary,这点在AFNetworkingManager中的已经写明:
NSDictionary *resultDic = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil];

使用JSONObjectWithData后我们得到了NSDictionary格式的数据,但是我们需要的上面自定义对象模型数据啊!所以,你还需要一个序列化的工具在字典和模型间自如的转化,它就是SerializationTools!

// 转化为字典
- (NSMutableDictionary *)ToDictionary:(NSObject *)obj; // 获取属性数组
- (NSMutableDictionary *)ToKeyDictionary:(NSObject *)obj; // 字典填充对象
- (id)ToObjectOfDictionary:(NSDictionary *)dic class:(Class)class; // 转化为字典
- (NSData *)ToNSData:(NSObject *)obj; // 字典填充对象
- (id)ToObjectOfData:(NSData *)data class:(Class)class; /**
* 字典数组转换为对象数组
*
* @param class 对象类别名称
* @param array 数组
*
* @return 对象数组
*/
-(NSMutableArray *)GetObjectListOfArray:(Class)class array:(NSArray *)array; /**
* 对象数组转换为字典数组
*
* @param array 对象数组
*
* @return 字典数组
*/
-(NSArray *)GetDicListOfArray:(NSMutableArray *)array; /**
* json格式字符串转字典
*
* @param jsonString <#jsonString description#>
*
* @return <#return value description#>
*/
+ (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString; /**
* 字典转json字符串
*
* @param dic <#dic description#>
*
* @return <#return value description#>
*/
+ (NSString*)dictionaryToJson:(NSDictionary *)dic;

序列化工具类

值得注意的是这段代码,利用runtime获取模型的字段属性(代码段落,全部代码请移步SerializationTools实现类.m)

// 获取类成员变量和属性列表,ivarsCnt为类成员数量
unsigned int ivarsCnt = ; Ivar *ivars = class_copyIvarList(cls, &ivarsCnt); // 只获取类属性列表
// unsigned int outCount = 0;
// objc_property_t *properties =class_copyPropertyList(cls, &outCount); // 遍历成员变量列表,其中每个变量都是Ivar类型的结构体
for (const Ivar *p = ivars; p < ivars + ivarsCnt; ++p) { Ivar const ivar = *p; // 获取变量名
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 若此变量未在类结构体中声明而只声明为Property,则变量名加前缀 '_'下划线
// 比如 @property(retain) NSString *abc;则 key == _abc;
id value = [obj valueForKey:key]; if([key characterAtIndex:]=='_'){
key=[key substringFromIndex:];
}
if (value) {
[dictionaryFormat setObject:[value class] forKey:key];
} else {
// 获取类名
NSString *className = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; [dictionaryFormat setObject:[self GetClassByName:className] forKey:key];
}
}

通过序列化工具我们就可以很轻松的将字典转化为自定义模型了!

id resultJson = [[SerializationTools sharedInstance] ToObjectOfDictionary:self.resultDic class:class];

3、Service逻辑计算与服务提供

service层主要是根据实际业务需求来定义的接口,实现逻辑计算与业务组装,在这里我举一个例子如:

// 返回成功的结果、返回失败的信息、请求超时的错误都能通过block实现反向传值

.h文件

// GET
- (void)getStudentRecords:(NSInteger)offset
length:(NSInteger)length
parentView:(UIView *)parentView
blockRtn:(void (^)(ZTTestModelResult *))blockRtn
blockError:(void (^)(JsonCommonResultBase*))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut; -------------------------------分割线------------------------------------- .m文件 - (void)getStudentRecords:(NSInteger)offset
length:(NSInteger)length
parentView:(UIView *)parentView
blockRtn:(void (^)(ZTTestModelResult *))blockRtn
blockError:(void (^)(JsonCommonResultBase*))blockError
blockTimeOut:(TimeOutCompletion)blockTimeOut
{
NSString *strUrl = @"对接的api url"; [[ZTHttpManager sharedInstance]
getDataToUrl:strUrl
headers:nil
params:nil
parentView:parentView
showShadow:YES
class:[ZTTestModelResult class]
blockRtn:blockRtn
blockError:blockError
blockTimeOut:blockTimeOut];
}

4、Controller层业务诉求

// Get
[service getStudentRecords: length: parentView:self.view blockRtn:^(ZTTestModelResult *arryRtn) { // 回调成功,处理后续逻辑 } blockError:^(JsonCommonResultBase *error) { // show message about error } blockTimeOut:^{ // show message about timeout
}]; // Post
[service postTest:@"param1" param2:@"param2" param3:@"param3" parentView:self.view blockRtn:^(JsonCommonResultBase *result) { // 回调成功,处理后续逻辑 } blockError:^(JsonCommonResultBase *error) { // show message about error } blockTimeOut:^{ // show message about timeout
}]; // upload
[service uploadFileExpImage:[UIImage new] parentView:self.view blockProgress:^(NSString *progress) { // 上传进度回调成功,处理显示逻辑,注意刷新UI的操作一定要在主线程
dispatch_async(dispatch_get_main_queue(), ^{ //
}); } blockRtn:^(JsonCommonResultBase *rtn) { // 回调成功,处理后续逻辑 } blockError:^(JsonCommonResultBase *error) { // show message about error } blockTimeOut:^{ // show message about timeout }];

综上所述,一个基本的基于MVC的网络数据访问框架就完成了!

github地址:https://github.com/BeckWang0912/ZTAFNetworking.git

BTW:demo中主要是框架的搭建和AFNetworking的封装,不保证完全适用每个人的项目,我只提供设计思路,您来个性化,有不足之处还希望各位老司机多多包涵,如果对您又些许帮助的话,请在github上标个星星,您的鼓励是我写作的动力,不胜感激!

上一篇:SGU 117.Counting


下一篇:(三)学习JavaScript之getElementsByTagName方法