勤之时 - 网络层的实现

事实上,【勤之时】一个本地应用,基本上没有什么网络请求,或者需要从服务器端下载数据。不过为了美观,每天的背景图片会变化,并且会有一个每日故事的分享页面,也就是说,每天一图。

那么具体这个图片怎么来?当然可以自己搭建服务器,然后提供图片。不过这样相对来说比较复杂,不但要搭建服务器,还要自己准备图片。所以最简单的是看有没有现成的API 服务,我找到了两个:

  1. Bing 的每日一图
  2. Unsplash API

Bing的每日一图并不是API服务,不过通过抓包,可以发现http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1 可以获取到无水印的图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

{
"images": [{
"startdate":"20170319",
"fullstartdate":"201703191600",
"enddate":"20170320",
"url":"/az/hprichbg/rb/TingSakura_ZH-CN14945610051_1920x1080.jpg",
"urlbase":"/az/hprichbg/rb/TingSakura_ZH-CN14945610051",
"copyright":"一只在樱花树上嬉戏的绿绣眼(© Reece Cheng/500px)",
"copyrightlink":"http://www.bing.com/search?q=%E6%98%A5%E5%88%86&form=hpcapt&mkt=zh-cn",
"quiz":"/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20170319_TingSakura%22&FORM=HPQUIZ",
"wp":true,
"hsh":"c4afb61170efde716ed371c02f8db13f","drk":1,"top":1,"bot":1,"hs":[]
}],
"tooltips": {
"loading":"正在加载...",
"previous":"上一个图像",
"next":"下一个图像",
"walle":"此图片不能下载用作壁纸。",
"walls":"下载今日美图。仅限用作桌面壁纸。"
}
}

从返回的Json中可以看到,我们可以获取到urlbase,经过测试,bing提供了1080x1920的图片,其后缀为_1080x1920.jpg,所以最后无水印图片的下载地址为http://www.bing.com/urlbase_1080x1920.jpg。

那么当前图片对应的故事在哪里呢?同样发现了API接口http://cn.bing.com/cnhp/coverstory/,不过这里的图片是有水印的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"date":"March 20",
"title":"碧玉妆 绿丝绦",
"attribute":"枝头嬉戏的绿绣眼",
"para1":"古代的春分分为三候:“一候玄鸟至,二候雷乃发声,三候始电。”便是说春分日后,燕子便从南方飞来了,下雨时天空便要打雷并发出闪电。春分后,中国南方大部分地区越冬作物进入春季生长阶段,气温也继续回升,真正的春天来了,你也开始一个全新的自己吧!",
"para2":"",
"provider":"© Reece Cheng/500px",
"imageUrl":"/th?id=OSA.pfFS41jcab0yuA&pid=SatAns&w=100&h=100&c=8&rs=1","primaryImageUrl":"http://hpimges.blob.core.chinacloudapi.cn/coverstory/watermark_tingsakura_zh-cn14945610051_1920x1080.jpg",
"Country":"",
"City":"",
"Longitude":"",
"Latitude":"",
"Continent":"亚洲",
"CityInEnglish":"Enter keyword",
"CountryCode":""
}

所以综合这两个,我们就可以得到每日背景图片及故事。

至于Unsplash,大家可以参考Unsplash官网的说明去试试。【勤之时】暂时使用的是Bing的每日一图及故事来作为背景。

知道了需求,了解了内容的来源,接下来就是如何实现了。同样,iOS应用架构谈 网络层设计方案探讨了详细的方案。

最终的方案

  1. 使用delegate来做数据对接
  2. 交付NSDictionary给业务层,使用Const字符串作为Key来保持可读性

此外,【勤之时】的需求涉及到同步问题,可以发现,我们的操作为

  1. 访问http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1 获取Json数据
  2. 根据Json数据拼接获得每日图片的下载地址
  3. 下载图片
  4. 访问http://cn.bing.com/cnhp/coverstory/获取每日故事的内容。

可以发现,我们会发送3个HTTP异步请求,只有当三个请求全部成功返回后,我们才能认为这个下载任务完成。因此,需要使用dispatch_group_t, dispatch_group_enter, dispatch_group_leave,dispatch_group_notify来完成这个组合。

最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//  ILDNetwork.h

#import <Foundation/Foundation.h>

@protocol ILDNetworkDelegate <NSObject>

- (void)fetchStoryDataSuccess:(NSDictionary *)storyDataDictionary;
- (void)fetchStoryDataFail:(NSError * _Nonnull)error;

@end

@interface ILDNetwork : NSObject

@property (nonatomic,weak) id<ILDNetworkDelegate> delegate;

- (void)downloadStoryData;

@end

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

![
![网络层需求.png](http://upload-images.jianshu.io/upload_images/1771779-726f82b81d60f3da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](http://upload-images.jianshu.io/upload_images/1771779-541fcb4c8382a9f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)// ILDNetwork.m

#import "ILDNetwork.h"
#import "ILDNetworkConstants.h"
#import <UIKit/UIKit.h>
#import <AFNetworking/AFNetworking.h>

@interface ILDNetwork()

@property (nonatomic, strong) dispatch_group_t requestGroup;

@property (nonatomic, strong) NSString *storyTitle;
@property (nonatomic, strong) NSString *storyAttribute;
@property (nonatomic, strong) NSString *storyPara1;
@property (nonatomic, strong) UIImage *storyImage;
@property (nonatomic, strong) NSError *error;

@end


@implementation ILDNetwork

- (void)downloadStoryData {
[self downloadImageData];
[self downloadOtherData];

dispatch_group_notify(self.requestGroup, dispatch_get_main_queue(), ^{
if (!self.error) {
[self.delegate fetchStoryDataFail:self.error];
} else {
NSDictionary *storyDataDictionary = @{
kBingStoryURLTitle:self.storyTitle,
kBingStoryURLAttribute:self.storyAttribute,
kBingStoryURLPara1:self.storyPara1,
kBingImageURLImageData:UIImageJPEGRepresentation(self.storyImage, 0.5)
};
[self.delegate fetchStoryDataSuccess:storyDataDictionary];
}
});
}

- (void)downloadImageData {
dispatch_group_enter(self.requestGroup);
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSURL *URL = [NSURL URLWithString:kBingImageURL];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
self.error = error;
} else {
NSDictionary *responseDict = (NSDictionary *)responseObject[kBingImageURLImages][0];
NSString *imageURLString = [NSString stringWithFormat:kBingImageFullPathFormat, responseDict[kBingImageURLUrlBase], kImageTypes];
[self downloadImageData:imageURLString];
}
dispatch_group_leave(self.requestGroup);
}];

[dataTask resume];
}

- (void)downloadImageData:(NSString *)imageUrlString {
dispatch_group_enter(self.requestGroup);
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
manager.responseSerializer = [AFImageResponseSerializer serializer];

NSURL *URL = [NSURL URLWithString:imageUrlString];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
self.error = error;
} else {
self.storyImage = responseObject;
}
dispatch_group_leave(self.requestGroup);
}];

[dataTask resume];
}

- (void)downloadOtherData {
dispatch_group_enter(self.requestGroup);
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSURL *URL = [NSURL URLWithString:kBingStoryURL];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
self.error = error;
} else {
NSDictionary *responseDict = (NSDictionary *)responseObject;
self.storyTitle = responseDict[kBingStoryURLTitle];
self.storyAttribute = responseDict[kBingStoryURLAttribute];
self.storyPara1 = responseDict[kBingStoryURLPara1];
}
dispatch_group_leave(self.requestGroup);
}];

[dataTask resume];
}

- (dispatch_group_t)requestGroup {
if (!_requestGroup) {
_requestGroup = dispatch_group_create();
}

return _requestGroup;
}

@end