跟不上时代的人突然间走在了时代的前列,果然有别样的风景。首先鄙视一下AFNetworking。这个东西实在太难用了。不想封装都不行,要不写一大堆代码。
1
2
3
4
5
6
7
8
9
10
11
|
NSURL
*URL = [
NSURL
URLWithString:@
"http://example.com/resources/123.json"
];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:URL.absoluteString parameters:
nil
progress:
nil
success:^(
NSURLSessionTask
*task,
id
responseObject) {
NSLog
(@
"JSON: %@"
, responseObject);
}
failure:^(
NSURLSessionTask
*operation,
NSError
*error) {
NSLog
(@
"Error: %@"
, error);
}
];
|
Http请求
但是用alamofire就简单的很多了,如:
1
2
3
4
|
Alamofire
.
request
(.
GET
,
"https://httpbin.org/get"
,
parameters
: [
"foo"
:
"bar"
])
.
response
{
request
,
response
,
data
,
error
in
print
(
response
)
}
|
都是一个GET请求,但是可见的是Alamofire代码量少很多。这也是和AFNetworking3.x比较了,如果你用的是AFNetworking2.x的话代码量的对比更加明显。对于程序员来说调用方法的API简单方便就是用户体验。Developer们也是需要满足UE的需要的。
下面开始进入正题。下面用请求微博的time line来做栗子。
1
2
3
4
5
6
|
parameters
= [
"access_token"
:
weiboUserInfo
.
accessToken
??
""
,
"source"
:
ConstantUtil
.
WEIBO_APPKEY
]
//1
Alamofire
.
request
(.
GET
,
""
//
2
,
parameters
:
parameters
,
encoding
: .
URL
,
headers
:
nil
)
.
responseString
(
completionHandler
: {
response
in
print
(
"response:- \(
response
)"
)
//3
})
|
这里用Alamofire请求微博的time line。
1. 请求微博的time line就需要SSO或者网页方式登录微博之后从服务器返回的access_token。另外一个必须的输入参数就是添加微博应用的时候生成的app key。
2. 请求的url。
这个url返回的就是你follow的好友的微博。就是你一打开微博客户端看到的那些。
3. 我们知道Alamofire可以把请求返回的数据转化为JSON、String和NSData。如果是作为JSON来处理,也就是使用了responseJSON
方法的话,JSON数据会被自动转化为NSDictionary
。我们后面需要用到字符串来实现json字符串和Model对象的匹配,所以我们用方法responseString
。
如果一切设置正确,你会看到这样的结果:
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
|
{
"statuses"
: [
{
"created_at"
:
"Tue May 31 17:46:55 +0800 2011"
,
"id"
:
11488058246
,
"text"
:
"求关注。"
,
"favorited"
:
false
,
"truncated"
:
false
,
"in_reply_to_status_id"
:
""
,
"in_reply_to_user_id"
:
""
,
"in_reply_to_screen_name"
:
""
,
"geo"
:
null
,
"mid"
:
"5612814510546515491"
,
"reposts_count"
:
8
,
"comments_count"
:
9
,
"annotations"
: [],
"user"
: {
"id"
:
1404376560
,
"screen_name"
:
"zaku"
,
"name"
:
"zaku"
,
"province"
:
"11"
,
"city"
:
"5"
,
"location"
:
"北京 朝阳区"
,
"description"
:
"人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。"
,
"url"
:
"http://blog.sina.com.cn/zaku"
,
"profile_image_url"
:
"http://tp1.sinaimg.cn/1404376560/50/0/1"
,
"domain"
:
"zaku"
,
"gender"
:
"m"
,
"followers_count"
:
1204
,
...
}
},
...
],
"ad"
: [
{
"id"
:
3366614911586452
,
"mark"
:
"AB21321XDFJJK"
},
...
],
"previous_cursor"
:
0
,
// 暂时不支持
"next_cursor"
:
11488013766
,
// 暂时不支持
"total_number"
:
81655
}
|
以上是微博给出来的例子的一部分,我们来看看我们需要什么。我们需要一部分文字和一部分的图片。之后要显示的内容主要就是文字或者图片。
解析
我们用ObjectMapper
解析json。ObjectMapper
是一个双向的转化工具。可以把json字符串转化成model也可以把model转化成json字符串。
安装ObjectMapper
:
pod 'ObjectMapper', '~> 1.1'
ObjectMapper
对于json的解析都是从外往内进行的,这个层层解析的过程中一般没有特殊指定的话每一层都不能少(可以通过制定解析路径减少)。每一层都需要配备一个实体类。
最外面的一层是:
1
2
3
4
5
6
7
8
|
{
"statuses"
: [
...
],
"previous_cursor"
:
0
,
"next_cursor"
:
11488013766
,
"total_number"
:
81655
}
|
所以对应的model定义是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import
ObjectMapper
class
BaseModel
:
Mappable
{
// 1
var
previousCursor
:
Int
?
var
nextCursor
:
Int
?
//var statuses
var
totalNumber
:
Int
?
required
init
?(
_
map
:
Map
) {
// 2
}
func
mapping
(
map
:
Map
) {
// 3
previousCursor
<
-
map
[
"previous_cursor"
]
nextCursor
<
-
map
[
"next_cursor"
]
//hasVisible <- map["hasvisible"]
statuses
<
-
map
[
"..."
]
// 4
totalNumber
<
-
map
[
"total_number"
]
}
}
|
最重要的是先import ObjectMapper
。没有这个什么都干不了。
1. BaseModel
类需要实现Mappable
接口。后面就是这个protocol
的实现。
2. 返回可能为空对象的初始化方法,法暂时用不到。
3. 这个方法最关键了。在这个方法里指定json的值对应的是model里的哪个属性。这部分功能可以自动实现,哪位有心人可以fork出来写一个,也方便大家使用。
4. 请看下文。
在深入一层
上问的标签4的内容我们在这里详细介绍。我们要展示的内容都是在statuses下的。那么我们应该如何处理这部分的内容呢?statuses的json格式是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"statuses"
: [
{
"created_at"
:
"Tue May 31 17:46:55 +0800 2011"
,
"id"
:
11488058246
,
"text"
:
"求关注。"
,
"favorited"
:
false
,
"truncated"
:
false
,
"in_reply_to_status_id"
:
""
,
"in_reply_to_user_id"
:
""
,
"in_reply_to_screen_name"
:
""
,
"geo"
:
null
,
...
}
],
}
|
可以有两个方式来处理深层的json数据。一个是在mapping
方法里指定json数据和属性的对应关系。比如在BaseMode
类中映射statuses中的text可以这样写:
1
2
3
4
5
6
7
8
9
10
|
class
BaseModel
{
var
text
:
String
?
required
init
?(
_
map
:
Map
) {
}
func
mapping
(
map
:
Map
) {
self
.
text
<
-
map
[
"statuses.text"
]
}
}
|
但是这样是错误的!因为statuses是一个数组,而不是一个对象。只有statuses对应的是一个对象的时候才适用于这个情况。
对上面的代码进行修改,让其适用于数据的情况。
1
2
3
4
5
6
7
8
9
10
|
class
BaseModel
{
var
text
:
String
?
required
init
?(
_
map
:
Map
) {
}
func
mapping
(
map
:
Map
) {
self
.
text
<
-
map
[
"status.0.text"
]
}
}
|
self.text <- map["statuses.0.text"]
中间的数字零说明text属性对应的是json中的statuses数组的第一个元素的text的值。但是在statuses下会有很多个json对象,一个一个的挨个解析的方式显然是不适合的。更不用说这才两层,有多少奇葩的API返回的是三层甚至更多的?
那么就剩下最后的一种方法了。内层json的model类继承外层的json的model类。按照这个方法那么我们为statuses对应的json对象定义一个model类为StatusModel
。由于StatusModel
对应的是内层的json对象,那么就需要继承外层的json对象的类,也就是BaseModel
。刚开始就命名为BaseModel
应该是已经露馅了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class
StatusModel
:
BaseModel
{
// 1
var
statusId
:
String
?
var
thumbnailPic
:
String
?
var
bmiddlePic
:
String
?
var
originalPic
:
String
?
var
weiboText
:
String
?
var
user
:
WBUserModel
?
required
init
?(
_
map
:
Map
) {
super
.
init
(
map
)
// 2
}
override
func
mapping
(
map
:
Map
) {
super
.
mapping
(
map
)
// 2
statusId
<
-
map
[
"id"
]
thumbnailPic
<
-
map
[
"thumbnail_pic"
]
bmiddlePic
<
-
map
[
"bmiddle_pic"
]
originalPic
<
-
map
[
"original_pic"
]
weiboText
<
-
map
[
"text"
]
}
}
|
- 也就是我们说的json对象嵌套时的model类的继承关系。
- 在这种继承关系中需要十分注意的是。在
Mappable
协议的方法的调用中需要先调用基类的对应方法,super.init(map)
和super.mapping(map)
。至于说mapping
方法的映射关系,每个json对象对应的model类只管这一个对象的就可以。
那么在最外层的BaseModel
类中的statuses属性也就可以给出一个正确的完整的写法了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class
BaseModel
:
Mappable
{
var
previousCursor
:
Int
?
var
nextCursor
:
Int
?
var
hasVisible
:
Bool
?
var
statuses
: [
StatusModel
]?
// 1
var
totalNumber
:
Int
?
required
init
?(
_
map
:
Map
) {
}
func
mapping
(
map
:
Map
) {
previousCursor
<
-
map
[
"previous_cursor"
]
nextCursor
<
-
map
[
"next_cursor"
]
hasVisible
<
-
map
[
"hasvisible"
]
statuses
<
-
map
[
"statuses"
]
// 2
totalNumber
<
-
map
[
"total_number"
]
}
}
|
- 内层的statuses数组直接调用内层json对象对应的model类的数组,也即是
var statuses: [StatusModel]?
。 - 在
mapping
方法中指定属性和json对象的关系,这里是statuses <- map["statuses"]
。
这样ObjectMapper
就知道应该如何解析json字符串到对应的类对象中了。除了上面提到的,ObjectMapper
还有很多其他的功能。如果需要了解更多可以查看官方文档。
那么从http请求,到返回数据,到解析json串的一系列动作就可以完整的联结起来了。最开始介绍使用Alamofire请求并成功返回之后,我们只是把字符串打印了出来。现在可以调用map方法来匹配json串和我们定义好的model类了。
1
2
3
4
5
6
7
8
9
10
11
12
|
parameters
= [
"access_token"
:
weiboUserInfo
.
accessToken
??
""
,
"source"
:
ConstantUtil
.
WEIBO_APPKEY
]
Alamofire
.
request
(.
GET
,
""
,
parameters
:
parameters
,
encoding
: .
URL
,
headers
:
nil
)
.
responseString
(
completionHandler
: {
response
in
print
(
"response:- \(
response
)"
)
let
statuses
=
Mapper
<
BaseModel
>
().
map
(
response
.
result
.
value
)
// 1
print
(
"total number: \(
statuses
!.
totalNumber
)"
)
if
let
timeLine
=
statuses
where
timeLine
.
totalNumber
>
0
{
// 2
self
.
timeLineStatus
=
timeLine
.
statuses
self
.
collectionView
?.
reloadData
()
}
})
|
- 使用
Mapper().map(response.result.value)
方法来映射json串。这里需要分开来看。Mapper()
初始化了一个Mapper对象。Mapper
是一个泛型,类型参数就是我们定义的最外层的json对象对应的model类BaseModel
。之后我们调用了这个初始化好的Mapper
对象的map
方法。这个方法的参数就是一个json串,也就是字符串类型的,但是这个字符串必须是json格式的。response.result.value
取出了http请求之后返回的json串。 map
方法返回的是可空类型的。所以需要用if-let
的方式检查一下返回的值是否可用。在可用的情况下用where
语句判断返回的timeLine总数是否大于零。大于零才是有意义的,才刷新collection view。