iOS

网络请求重构说明

这周主要在做网络请求的重构,在参考了很多开源的项目之后,做了一个基本的Demo。本文主要讲一下基本的使用方法,不涉及原理。

###背景
参考的项目有

  • Mantle:模型层的的封装框架,解决了json转object的问题。
  • YTKNetwork:网络请求的封装,基于AFNetworking,提供了普通请求,批量请求和依赖请求的处理。回调方法支持block和delegate两种模式。

用法

如何创建一个请求

程序启动完成后先配置一下baseUrl:

1
2
LCNetworkConfig *config = [LCNetworkConfig sharedInstance];
config.baseUrl = @"http://;

创建请求首先要继承一个请求类,并声明初始化方法,初始化方法里面的参数就是请求要用到的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//.h文件
@interface UserLoginApi : LCRequest

- (id)initWithLoginName:(NSString *)loginName
password:(NSString *)password;

@end

//.m文件
@implementation UserLoginApi {
NSString *_loginName;
NSString *_password;
}

//初始化方法
- (id)initWithLoginName:(NSString *)loginName
password:(NSString *)password
{
self = [super init];
if (self) {
_loginName = loginName;
_password = password;
}
return self;
}

//请求的api,必须实现,如果有path类型的参数,需要从初始化方法里面把参数传进来,然后在这里拼装
- (NSString *)requestUrl {
return @"/login";
}

//请求方法,必须实现
- (LCRequestMethod)requestMethod {
return LCRequestMethodPost;
}

//请求参数合成,如果没有请求参数,也可以不实现
- (id)requestArgument {
NSString* tokenId = [USER_DEFAULT objectForKey:USERDEFAULTS_KEY_TOKENID];
NSString* clientId = [USER_DEFAULT objectForKey:USERDEFAULTS_KEY_REGISTERID];
return @{
@"loginName": _loginName,
@"password": _password,
@"source": @"iphone",
@"sign": @"ios",
@"versionNo": [LCSystemUtil appVersion],
@"iosTokenId": [LCStringUtil safeString:tokenId],
@"clientId": [LCStringUtil safeString:clientId]
};
}

@end

实现了请求的Api对象之后,就可以发送请求了,发送请求有两种方式:Block和Delegate:
Block方式:

1
2
3
4
5
6
CityQueryApi* api = [[CityQueryApi alloc] init];
[api startWithCompletionBlockWithSuccess:^(LCBaseRequest *request) {

} failure:^(LCBaseRequest *request) {

}];

Delegate方式:

1
2
3
4
5
6
7
8
9
10
11
CityQueryApi* api = [[CityQueryApi alloc] init];
api.delegate = self;
[api start];

- (void)requestFinished:(LCBaseRequest *)request
{
}

- (void)requestFailed:(LCBaseRequest *)request
{
}

如果一个页面只有一个请求,我觉得用Block比较直观,回调代码就在请求边上;如果有多个请求,用Delegate便于管理,因为回调处理代码都集中在一个地方。

######json转Model
json转Model使用了Mentle。原来转换起来比较麻烦,一是不能自定义属性名,接口返回json的key是什么,我们的属性就是什么;二是如果返回的对象是多层的,就需要手动的一层层解包,比较麻烦。

而Mentle完全解决了这些问题,并且提供了更多的功能(序列化什么的)。
首先,我们建立一个模型类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
json文件
{
nickName = nmj;
phone = 18512553387;
photoUrl = "<null>";
state = "<null>";
userId = 705363918947328;
}

//.h文件
@interface LoginUserModel : MTLModel <MTLJSONSerializing>

@property (nonatomic,strong) NSNumber *userId;

@property (nonatomic,copy) NSString *phone;

@property (nonatomic,copy) NSString *nickName;

@property (nonatomic,copy) NSString *avatarUrl;

@property (nonatomic,copy) NSString *loginState;

@end

//.m文件
@implementation LoginUserModel

//此方法必须实现,用来标记本地属性名和接口key之间的匹配
//如果属性名和接口key完全一致,则返回nil,但方法一定要写。
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return @{
@"avatarUrl": @"photoUrl",
@"loginState": @"state"
};
}

@end

然后在请求的回调里面获得model

1
2
NSError *error = nil;
LoginUserModel* model = [MTLJSONAdapter modelOfClass:LoginUserModel.class fromJSONDictionary:request.responseJSONObject error:&error];

如果是返回嵌套的对象,只需要增加一个方法,可以在CityGroupModel里面查到,它里面cityList对应的是一个数组,数组里面的每一个对象都是CityModel,Mantle会递归的进行解析,不用我们操心。

1
2
3
4
+ (NSValueTransformer *)cityListJSONTransformer
{
return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CityModel.class];
}

HUD

HUD以前写起来比较麻烦,所以我把HUD的展示、消失、错误提示封装在请求里面了:一共两个属性,一个方法。

1
2
3
4
5
6
7
8
//是否展示HUD
@property (nonatomic,assign) BOOL showHUD;

//hud的文字
@property (nonatomic,copy) NSString* HUDText;

//显示错去消息的方法
- (void)showHUDError:(NSString*)msg;

如果不需要显示HUD,那么请求的时候什么也不用做,否则需要设置以上两个属性。如果请求出错需要提示的话,就在failure的block里面提示一下。

1
2
3
4
5
6
7
8
AACoinExchangeCodeApi* api = [[AACoinExchangeCodeApi alloc] initWithPayer:[UserInfoEntity shareEntity].phone tradeType:@"1" accType:accType amount:amount];
api.showHUD = YES;
api.HUDText = NSLocalizedString(@"Obtaining verification code", nil);
[api startWithCompletionBlockWithSuccess:^(LCBaseRequest *request) {
[self.getCodeButton beginCountDown];
} failure:^(LCBaseRequest *request) {
[request showHUDError:request.errorMessage];
}];