一劳永逸的解决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封装

针对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 = 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;
}
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 = 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上标个星星,您的鼓励是我写作的动力,不胜感激!

 

posted @ 2017-06-20 14:21  贝克的飞机  阅读(1180)  评论(2编辑  收藏  举报