一劳永逸的解决AFNetworking3.0网络请求问题
AFNetworking在iOS网络请求第三方库中占据着半壁江山,前段时间将AFNetworking进行了3.0版本的迁移,运用面向对象的设计将代码进行封装整合,这篇文章主要为还在寻找AFNetworking集成代码或者准备3.0迁移的各位童鞋们提供思路,同时自定义了字典模型转换方法,需要的朋友也可以作为参考,还望各位老司机批评指正!先上代码框架图:
1、DB数据访问层,在AFNetworkingManager中我将AFNetworking的GET/POST/DELETE/PUT方法封装,提供了以下接口:
1 /** 2 * get方式请求数据 3 * 4 * @param strUrl api地址 5 * @param headers 头部信息 6 * @param params 可变参数信息 7 * @param class 返回数据模型类 8 * @param block block结果回调 9 * @param blockError block错误回调 10 * @param blockTimeOut block超时回调 11 */ 12 -(void)getDataFromUrl:(NSString *)strUrl 13 headers:(NSDictionary *)headers 14 params:(NSDictionary *)params 15 class:(Class)class 16 block:(CompletionLoad)block 17 blockError:(void (^)(JsonCommonResultBase *))blockError 18 blockTimeOut:(TimeOutCompletion)blockTimeOut; 19 20 /** 21 * post方式更新数据 22 * 23 * @param strUrl api地址 24 * @param headers 头部信息 25 * @param params 可变参数信息 26 * @param class 返回数据模型类 27 * @param block block结果回调 28 * @param blockError block错误回调 29 * @param blockTimeOut block超时回调 30 */ 31 - (void)postDataFromUrl:(NSString*)strUrl 32 headers:(NSDictionary*)headers 33 params:(NSDictionary*)params 34 class:(Class)class 35 block:(CompletionLoad)block 36 blockError:(void(^)(JsonCommonResultBase *))blockError 37 blockTimeOut:(TimeOutCompletion)blockTimeOut; 38 39 /** 40 * put方式更新数据 41 * 42 * @param strUrl api地址 43 * @param headers 头部信息 44 * @param params 可变参数信息 45 * @param class 返回数据模型类 46 * @param block block结果回调 47 * @param blockError block错误回调 48 * @param blockTimeOut block超时回调 49 */ 50 - (void)putDataFromUrl:(NSString*)strUrl 51 headers:(NSDictionary*)headers 52 params:(NSDictionary*)params 53 class:(Class)class 54 block:(CompletionLoad)block 55 blockError:(void(^)(id))blockError 56 blockTimeOut:(TimeOutCompletion)blockTimeOut; 57 58 /** 59 * delete方式删除数据 60 * 61 * @param strUrl api地址 62 * @param headers 头部信息 63 * @param params 可变参数信息 64 * @param class 返回数据模型类 65 * @param block block结果回调 66 * @param blockError block错误回调 67 * @param blockTimeOut block超时回调 68 */ 69 - (void)deleteDataFromUrl:(NSString*)strUrl 70 headers:(NSDictionary*)headers 71 params:(NSDictionary*)params 72 class:(Class)class 73 block:(CompletionLoad)block 74 blockError:(void(^)(JsonCommonResultBase *))blockError 75 blockTimeOut:(TimeOutCompletion)blockTimeOut; 76 77 /** 78 * post方式更新数据(上传文件如图片) 79 * 80 * @param strUrl api地址 81 * @param headers 头部信息 82 * @param params 可变参数信息 83 * @param dataFiles 文件数据 84 * @param class 返回数据模型类 85 * @param block block结果回调 86 * @param progressBlock block进度回调 87 * @param blockError block错误回调 88 * @param blockTimeOut block超时回调 89 */ 90 - (void)uploadDataFromUrl:(NSString *)strUrl 91 headers:(NSDictionary *)headers 92 params:(NSDictionary *)params 93 dataFiles:(NSArray *)dataFiles 94 progressBlock:(LoadProgress)progressBlock 95 block:(CompletionLoad)block 96 class:(Class)class 97 blockError:(void (^)(JsonCommonResultBase *))blockError 98 blockTimeOut:(TimeOutCompletion)blockTimeOut;
针对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;
好了,在这里完成了DB层的代码,访问API就毫无压力了!
2、模型基类JsonCommonResultBase/SerializationBaseModel
在这里我要说明一点,这里的模型基类是按照我们公司后台返回的API格式自定义,不一定适合每个人,但是可以作为各位的参考,具体的API返回数据结构为:
{ page = { hasMore = 0; totalRows = 1; }; 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 = 0; company = { code = alltrust; id = 16; }; totalPremiums = "9632.09"; totalPremiumsText = "\U00a59,632.09"; verifyStatus = Verified; } ); status = 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
#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
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 = 0; 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:0]=='_'){ key=[key substringFromIndex:1]; } 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:0 length:10 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上标个星星,您的鼓励是我写作的动力,不胜感激!