TestsTested | ✗ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Dec 2016 |
Maintained by Lk135*_*.
SGSHTTPModule(OC版本)是移动支撑平台 iOS Objective-C 组件之一,其思想借鉴了 YTKNetwork ,基于 AFNetworking 封装的一套 HTTP 请求组件。通过这套组件可以轻松实现 HTTP 请求、上传与下载
使用 AFNetworking 3.1 作为基础
SGSHTTPModule 可以通过 Cocoapods 进行安装,可以复制下面的文字到 Podfile 中:
target '项目名称' do
pod 'SGSHTTPModule', '~> 0.5.0'
end
使用 AFNetworking 3.1 版本进行封装,内部使用 NSURLSession
发起请求
SGSHTTPModule 提供了以下功能:
- 支持请求的发起、暂停、取消、继续操作
- 支持设置请求的优先级,充分利用 HTTP/2 的调度优势
- 支持不同的网络服务类型(标准网络传输、VoIP传输等)
- 支持统一设置服务器地址
- 可自行判断是否禁止蜂窝网络传输
- 支持不同的 URL Request 缓存策略
- 可自定义判断是否处理 cookies 数据
- 支持自定义对请求结果的过滤
- 支持 block 和 delegate 两种回调方式
- 支持断点续传功能
- 支持过滤重复下载
- 支持批量发起网络请求
- 支持依次发起具有依赖性的网络请求
基于面向对象的思想,将每个网络请求封装为一个对象,通过继承 SGSBaseRequest
,重写父类的一些必要方法来构造不同的网络请求
将请求操作与其他业务相隔离,降低代码的耦合度,减少 ViewController
(或者是业务 Model
)的代码量,让代码更简洁,更容易调试
并且通过继承的方式方便在基类中处理公共逻辑,以及对响应结果的持久化存储
- SGSBaseRequest:网络请求基础类
- SGSBatchRequest:批量发起网络请求
- SGSChainRequest:发起具有依赖性的网络请求
- SGSBaseRequest+Convenient:实现 AFHTTPSessionManager 请求风格的请求方法
- 辅助
- SGSHTTPConfig:统一设置服务器地址和缓存路径
- SGSRequestDelegate:请求过程的代理
- AFURLSessionManager+SGS:网络请求管理类
- 对象序列化
- SGSResponseObjectSerializable:方便请求返回对象模型数据
- SGSResponseCollectionSerializable:方便请求返回包含对象的集合数据
通过 SGSHTTPConfig
可以统一设置服务器地址、缓存路径以及安全策略,可以考虑放在App启动时进行这项操作
typedef NS_ENUM(NSInteger, SGSServer) {
SGSServerDev, // 开发环境
SGSServerRel, // 发布环境
};
// App 启动
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setupNetworkConfig:SGSServerDev];
return YES;
}
// 设置网络请求配置
- (void)setupNetworkConfig:(SGSServer)server {
SGSHTTPConfig *config = [SGSHTTPConfig sharedInstance];
switch (server) {
case SGSServerDev: // 开发环境
config.baseURL = @"http://192.168.10.xx";
break;
case SGSServerRel: // 发布环境
config.baseURL = @"http://110.123.10.xx";
break;
default:
break;
}
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
cachePath = [cachePath stringByAppendingPathComponent:@"RequestCache"];
// 默认的缓存路径为:~/Library/Caches/com.southgis.iMobile.HTTPModule.RequestCacheData
config.requestCacheDataPath = cachePath;
}
如果 SGSHTTPConfig 设置了 config.baseURL,那么这里的 urlString 可以是相对路径,例如:/path/someserver?name1=value1&name2=value2
[SGSBaseRequest GET:urlString success:^(SGSBaseRequest * _Nonnull request) {
NSLog(@"response: %@", request.response);
NSLog(@"string: %@", request.responseString);
NSLog(@"json: %@", request.responseJSON);
} failure:^(SGSBaseRequest * _Nonnull request) {
NSLog(@"failure: %@", request.error);
}];
responseFilter 为请求完毕回调之前的过滤,当 responseFilter 调用完毕后 只要 request 的 error 不为空,那么就会回调 failure 分支,否则回调 success 分支 因此可以在该闭包中统一过滤特定的响应数据格式
假设返回的JSON格式如下: { "code": 正确的code为200,错误的code为其他状态码 "results": 正确返回的结果 "description": 错误描述 }
[SGSBaseRequest GET:urlString parameters:params responseFilter:^(SGSBaseRequest * _Nonnull originalResponse) {
// 如果 request 的 error 不为空,那么都会回调 failure 分支
if (originalResponse.error != nil) return ;
id json = originalResponse.responseJSON;
if ((json == nil) || ![json isKindOfClass:[NSDictionary class]]) {
// 不是合法的数据格式错误
originalResponse.error = [NSError errorWithDomain:kSGSErrorDomain code:-8001 userInfo:@{NSLocalizedDescriptionKey: @"非法格式数据"}];
return;
}
// 服务端自定义的code
NSInteger code = [[json objectForKey:@"code"] integerValue];
if (code != 200) {
// 不是合法的自定义响应
NSString *desc = [json objectForKey:@"description"];
originalResponse.error = [NSError errorWithDomain:kSGSErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: desc ?: @"系统错误"}];
return;
}
// 只要 request 的 error == nil,那么都会回调 success 分支
id results = [json objectForKey:@"results"];
originalResponse.responseData = [NSJSONSerialization dataWithJSONObject:results options:kNilOptions error:NULL];
} success:^(SGSBaseRequest * _Nonnull request) {
NSLog(@"response: %@", request.response);
NSLog(@"string: %@", request.responseString);
NSLog(@"json: %@", request.responseJSON);
} failure:^(SGSBaseRequest * _Nonnull request) {
NSLog(@"failure: %@", request.error);
}];
forClass 指明最终所返回的 request.responseObject 或 request.responseObjectArray 的对象类型 如果 forClass 为空,那么 request.responseObject 或 request.responseObjectArray 就会返回 nil
[SGSBaseRequest GET:urlString
parameters:params
progress:nil
responseFilter:[self defaultResponseFilter]
forClass:[AccountInfo class]
success:^(SGSBaseRequest *request) {
AccountInfo *account = (AccountInfo *)request.responseObject;
NSLog(@"获取用户信息成功: %@", account.description);
} failure:^(SGSBaseRequest *request) {
NSLog(@"获取用户信息失败: %@", request.error);
}];
当一些请求都具有相同的行为时,可以编写一个通过继承 SGSBaseRequest
类的基础请求类
如果需要过滤的数据格式都是统一的,那么除了通过一个指定的方法(静态方法、extern等)传递过滤 block 外,使用基础请求类的方式也是可以达到统一过滤的效果
例如在这个基础请求类中定义响应数据的过滤,并且在发起请求前都可以打印请求地址
@interface MyBaseRequest : SGSBaseRequest
@end
@implementation MyBaseRequest
// 发起请求前的block
- (void (^)(__kindof SGSBaseRequest * _Nonnull))requestWillStartBlock {
return ^(SGSBaseRequest *request) {
// 打印请求地址和请求参数
NSLog(@"%@", [[NSURL URLWithString:[self originURLString] parameters:self requestParameters]]);
};
}
// 请求完毕过滤
- (void)requestCompleteFilter {
// 如果有错误直接返回
if (self.error != nil) {
return ;
}
id json = self.responseJSON;
// 不是JSON数据
if ((json == nil) || ![json isKindOfClass:[NSDictionary class]]) {
self.error = errorWithCode(ErrorCodeInvalidResponseValue, @"非法数据");
// 清空返回结果
self.responseData = nil;
return ;
}
// 返回的状态码不合法
NSInteger state = [[json objectForKey:@"code"] integerValue];
if (state != 200) {
NSString *desc = [json objectForKey:@"description"];
self.error = errorWithCode(state, desc);
NSLog(@"返回的状态码不合法: %@", json);
// 清空返回结果
self.responseData = nil;
return ;
}
// 获取结果
id result = [json objectForKey:@"results"];
if (result != nil) {
// 重置结果
self.responseData = [NSJSONSerialization dataWithJSONObject:result options:kNilOptions error:nil];
} else {
self.responseData = nil;
}
}
@end
接下来就直接使用这个 MyBaseRequest
发起请求即可
[MyBaseRequest GET:_urlString
parameters:_params
progress:nil
responseFilter:nil // 这里的过滤闭包可以传入空
forClass:[AccountInfo class]
success:^(__kindof SGSBaseRequest * _Nonnull request) {
AccountInfo *account = (AccountInfo *)request.responseObject;
NSLog(@"请求成功:\n%@", account.description);
} failure:^(__kindof SGSBaseRequest * _Nonnull request) {
NSLog(@"请求失败: %@", request.error);
}];
或者
[MyBaseRequest GET:_urlString
parameters:_params
success:^(__kindof SGSBaseRequest * _Nonnull request) {
NSLog(@"请求成功:\n%@", request.responseJSON);
} failure:^(__kindof SGSBaseRequest * _Nonnull request) {
NSLog(@"请求失败: %@", request.error);
}];
SGSBaseRequest
的 API 类// 定义一个 LoginAPI 类,并继承 SGSBaseRequest
// 该请求需要用户名和密码
@interface LoginAPI : SGSBaseRequest
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *password;
- (instancetype)initWithUsername:(NSString *)username password:(NSString *)password;
@end
// 类实现部分
@implementation LoginAPI
// 请求方法,如果不重写默认为GET请求
- (SGSRequestMethod)requestMethod {
return SGSRequestMethodPost;
}
// 重写 requestURL 方法,返回登录的请求地址
- (NSString *)requestURL {
return @"http://192.168.11.11/service/mobileLogin";
}
// 请求参数
- (id)requestParameters {
// 使用该方法避免传入 nil 造成创建字典崩溃
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:_username, @"username", _password, @"password", nil];
return params;
}
// 模型对象类型
// 只要返回可以解析的类型,可以直接使用 responseObject 直接获取对象
- (Class<SGSResponseObjectSerializable>)responseObjectClass {
// 该 AccountInfo 类包含 userId 和 nickname 两个属性
return [AccountInfo class];
}
@end
如果共用一个基础地址,可以声明一个基础请求父类,并重写他的 -baseURL
方法
// 基础请求父类
@interface FileService : SGSBaseRequest
@end
@implementation FileService
// 重写 baseURL 方法,返回请求的基础地址
- (NSString *)baseURL {
return @"http://192.168.11.11";
}
@end
#pragma mark - DownloadFileAPI
// 下载文件请求类
@interface DownloadFileAPI : FileService
@property (nonatomic, copy) NSString *fileId;
@end
@implementation DownloadFileAPI
// 当然,也可以直接在这里重写 -baseURL 方法,但是没有这个必要
//- (NSString *)baseURL {
// return @"http://192.168.11.11";
//}
/**
当发起请求时是根据 `originURLString` 属性作为请求地址,`originURLString` 的拼接规则如下:
1. -requestURL 返回的字符串是否包含了“http://”,如果包含直接使用该地址进行请求
2. 判断该类是否重写父类的 -baseURL 方法,如果有则与 -requestURL 进行组合并发起请求
3. 判断 SGSHTTPConfig 是否设置了 baseURL,如果有则将配置的基础地址与 requestURL 进行组合并发起请求
4. 如果都没有设置 baseURL,并且 requestURL 的头部没有包含“http://”,那么会尝试直接使用 requestURL 的链接发起请求
*/
- (NSString *)requestURL {
return @"/service/mobileLogin";
}
// 请求参数
- (id)requestParameters {
return (_fileId) ? @{@"fileId": _fileId} : nil;
}
@end
如果返回的结果有特定形式,可以通过重写 -requestCompleteFilter
来过滤请求结果
只要error属性不为空,在最终请求回调时自动判断为请求失败,回调 failureCompletionBlock
和代理方法
所以当请求结果不符合预期值的时候,可以将自定义的 NSError
复制给 error
属性
假设合法的JSON结果为:
{
"code": 200,
"description": null,
"result": {
"userId": "1001"
"nickname": "李四"
}
}
非法的JSON结果为:
{
"code": 301,
"description": "密码错误",
"result": null
}
@implementation SomeRequestAPI
// 自定义请求完毕的过滤
- (void)requestCompleteFilter {
// 如果有错误直接返回
if (self.error != nil) return ;
id json = self.responseJSON;
// 苹果定义的的JSON数据格式
// 最外层为字典,并且key的类型全部字符串类型
if ((json == nil) || ![json isKindOfClass:[NSDictionary class]]) {
// 不是JSON数据
self.error = errorWithCode(ErrorCodeInvalidResponseValue, @"非法数据");
NSLog(@"登录接口返回错误信息: %@", self.responseString);
// 清空返回结果
self.responseData = nil;
return ;
}
NSInteger state = [[json objectForKey:@"code"] integerValue];
if (state != 200) {
// 返回的状态码不合法
NSString *desc = [json objectForKey:@"description"];
self.error = errorWithCode(state, desc);
NSLog(@"返回的状态码不合法: %@", json);
// 清空返回结果
self.responseData = nil;
return ;
}
// 获取结果
id result = [json objectForKey:@"results"];
if (result != nil) {
// 重置结果
self.responseData = [NSJSONSerialization dataWithJSONObject:result options:kNilOptions error:nil];
} else {
self.responseData = nil;
}
}
@end
// 获取对象类型
- (void)loginWithUsername:(NSString *)username password:(NSString *)password {
LoginAPI *request = [[LoginAPI alloc] initWithUsername:username password:password];
[request startWithCompletionSuccess:^(__kindof SGSBaseRequest * _Nonnull request) {
// 回调block将在主线程中执行
NSLog(@"登录成功,当前线程: %@", [NSThread currentThread]); // Main Thread
// 使用模型接收返回结果
AccountInfo *account = request.responseObject;
// 在回调block执行完毕后会主动清空,打破循环引用
// 所以可以直接使用self而不用担心保留环问题
[self handleAccountInfo:account];
} failure:^(__kindof SGSBaseRequest * _Nonnull request) {
NSLog(@"登录失败,当前线程: %@", [NSThread currentThread]); // Main Thread
NSString *msg = request.error.localizedDescription;
[self showAlert:@"登录失败" message:msg];
}];
}
- (void)loginWithUsername:(NSString *)username password:(NSString *)password {
LoginAPI *request = [[LoginAPI alloc] initWithUsername:username password:password];
// 将self设置为代理
request.delegate = self;
// 开始请求
[request start];
}
#pragma mark - SGSRequestDelegate
// 请求成功
- (void)requestSuccess:(SGSBaseRequest *)request {
NSLog(@"登录成功,当前线程: %@", [NSThread currentThread]); // Main Thread
AccountInfo *account = request.responseObject;
[self handleAccountInfo:account];
}
// 请求失败
- (void)requestFailed:(SGSBaseRequest *)request {
NSLog(@"登录失败,当前线程: %@", [NSThread currentThread]); // Main Thread
NSString *msg = request.error.localizedDescription;
[self showAlert:@"登录失败" message:msg];
}
使用 -startWithCompletionSuccess:failure:
或者 -start
即可进行下载,但是使用这两个方法下载不支持断点续传功能
@interface HDImageAPI : SGSBaseRequest
@end
@implementation HDImageAPI
- (NSString *)requestURL {
return @"http://www.example.com/DownloadImg/32405513.jpg";
}
@end
- (IBAction)downloadHDImage:(UIButton *)sender {
// 创建请求
_downloadHDImage = [HDImageAPI new];
// 进度将会在主线程中执行
[_downloadHDImage setProgressBlock:^(NSProgress * _Nonnull progress) {
float pro = (float)progress.completedUnitCount / (float)progress.totalUnitCount;
_progressBar.progress = pro;
}];
// > 发起下载请求
[_downloadHDImage startWithCompletionSuccess:^(__kindof SGSBaseRequest * _Nonnull request) {
[self handleHDImage:request.responseData];
} failure:^(__kindof SGSBaseRequest * _Nonnull request) {
[self showAlert:@"高清大图下载失败" message:request.error.localizedDescription];
}];
}
如果希望使用断点续传功能可以使用 -downloadWithCompletionSuccess:failure:
或者 -startDownload
在发起下载请求前,根据下载地址自动判断本地是否有断点数据:如果没有断点数据将发起新的下载请求;如果有断点数据,那么将读取断点数据继续下载
当下载或者断点续传失败时,会在磁盘和缓存中保存这次请求的断点数据
并且使用这两个方法会过滤掉重复的请求,当发起多个请求时,上一次的下载请求将会取消并且保存断点数据,使用断点续传发起新的下载请求
并且可以重写 -downloadTargetPath
方法指定下载完成后的保存路径
@implementation ChineseMapAPI
- (NSString *)requestURL {
return @"http://www.onegreen.net/maps/m/a/zhongguo1.jpg";
}
// 下载完毕后的保存路径
- (NSURL * _Nonnull (^)(NSURL * _Nonnull, NSURLResponse * _Nonnull))downloadTargetPath {
return ^(NSURL * location, NSURLResponse * response) {
NSURL *docDir = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
if (docDir == nil) {
return location;
}
// 下载完毕后的保存路径:~/Document/(图片名)
return [docDir URLByAppendingPathComponent:[response suggestedFilename]];
};
}
@end
- (IBAction)downloadHDImage:(UIButton *)sender {
// 创建请求
_downloadChineseMap = [ChineseMapAPI new];
// 进度将会在主线程中执行
[_downloadChineseMap setProgressBlock:^(NSProgress * _Nonnull progress) {
float pro = (float)progress.completedUnitCount / (float)progress.totalUnitCount;
_progressBar.progress = pro;
}];
// > 使用带有断点续传功能的下载
[_downloadChineseMap downloadWithCompletionSuccess:^(__kindof SGSBaseRequest * _Nonnull request) {
[self handleChineseMap:request.responseData];
} failure:^(__kindof SGSBaseRequest * _Nonnull request) {
[self showAlert:@"中国地图下载失败" message:request.error.localizedDescription];
}];
}
同样的只需要自定义一个类继承自 SGSBaseRequest
并且重写父类的 -requestMethod
的方法并返回 SGSRequestMethodPost
,在 constructingBodyBlock
属性中拼接需要上传的数据即可
@interface UploadItem : SGSBaseRequest
@property (nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
@end
// 自定义UploadAPI
@interface UploadAPI : SGSBaseRequest
@end
@implementation UploadAPI
- (NSString *)requestURL {
return @"http://www.example.com";
}
// 上传需要指定为Post请求
- (SGSRequestMethod)requestMethod {
return SGSRequestMethodPost;
}
// 如果需要上传的数据已经明确,可以通过重写 constructingBodyBlock 的 getter 方法
// - (void (^)(id<AFMultipartFormData> _Nonnull))constructingBodyBlock {
// return ^(id <AFMultipartFormData> formData) {
// 拼接参数...
// };
//}
@end
- (void)uploadImage {
// 创建上传请求实例
__weak typeof(&*self) weakSelf = self;
UploadAPI *upload = [UploadAPI new];
[upload setConstructingBodyBlock:^(id<AFMultipartFormData> formData) {
// 以文件URL的形式拼接表单数据
NSURL *imgURL1 = [[NSBundle mainBundle] URLForResource:@"1" withExtension:@"jpg"];
if (imgURL1 != nil) {
NSError *error = nil;
[formData appendPartWithFileURL:imgURL1 name:@"image1" error:&error];
if (error != nil) {
NSLog(@"获取图片'1'失败:%@", error);
}
}
// 以数据的形式拼接表单数据
UIImage *img2 = [UIImage imageNamed:@"2"];
if (img2 != nil) {
NSData *imgData = UIImagePNGRepresentation(img2);
if (imgData != nil) {
[formData appendPartWithFormData:imgData name:@"image2"];
}
}
// 以文件URL和参数的形式拼接表单数据
NSURL *docDir = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *imgURL3 = [docDir URLByAppendingPathComponent:@"3.png"];
NSError *appendFileError = nil;
[formData appendPartWithFileURL:imgURL3 name:@"image3" fileName:@"3" mimeType:@"image/png" error:&appendFileError];
if (appendFileError != nil) {
NSLog(@"获取图片'3'失败:%@", appendFileError);
}
// 以数据和参数的形式拼接表单数据
NSData *img4 = [NSData dataWithContentsOfFile:@"../4.jpg"];
if (img4) {
[formData appendPartWithFileData:img4 name:@"image4" fileName:@"4" mimeType:@"image/jpeg"];
}
// 更多的拼接方法请参照 AFMultipartFormData
}];
// 设置显示上传进度
[upload setProgressBlock:^(NSProgress * progress) {
float pro = (float)progress.completedUnitCount / (float)progress.totalUnitCount;
weakSelf.progressBar.progress = pro;
}];
// 发起上传请求
[upload startWithCompletionSuccess:^(__kindof SGSBaseRequest * _Nonnull request) {
weakSelf.textView.text = [NSString stringWithFormat:@"上传成功:\n%@\n", request.responseString];
} failure:^(__kindof SGSBaseRequest * _Nonnull request) {
weakSelf.textView.text = [NSString stringWithFormat:@"上传失败:\n%@\n", request.error.localizedDescription];
}];
}
可以使用 SGSBatchRequest
对批量发起多个网络请求
当所有请求全部成功返回的时候,通过 SGSBatchRequest
的 successCompletionBlock
回调
当某个请求失败时,将会取消其他尚未响应的请求,并且通过 SGSBatchRequest
的 failureCompletionBlock
回调
// 创建 GitHub HTML 文本的请求
- (GitHubAPI *)githubRequest {
GitHubAPI *request = [GitHubAPI new];
[request setCompletionBlockWithSuccess:^(__kindof SGSBaseRequest * _Nonnull request) {
// github HTML请求成功的处理
} failure:^(__kindof SGSBaseRequest * _Nonnull request) {
// github HTML请求失败的处理
}];
return request;
}
// 创建新浪 HTML 文本的请求
- (SinaAPI *)sinaRequest {
SinaAPI *request = [SinaAPI new];
request.ignoreCache = YES; // 忽略缓存
[request setSuccessCompletionBlock:^(__kindof SGSBaseRequest * _Nonnull request) {
// 新浪HTML请求成功的处理
}];
[request setFailureCompletionBlock:^(__kindof SGSBaseRequest * _Nonnull request) {
// 新浪HTML请求失败的处理
}];
return request;
}
- (IBAction)loadHTML:(UIButton *)sender {
// 批量发起请求
SGSBatchRequest *batch = [[SGSBatchRequest alloc] initWithRequestArray:@[[self githubRequest], [self sinaRequest]]];
[batch startWithCompletionSuccess:^(SGSBatchRequest * _Nonnull batchRequest) {
NSLog(@"批处理的请求: %@", batchRequest.requestArray);
[self showAlert:@"批处理请求完毕" message:nil];
} failure:^(SGSBatchRequest * _Nonnull batchRequest, __kindof SGSBaseRequest * _Nonnull baseRequest) {
NSString *msg = baseRequest.error.localizedDescription;
[self showAlert:@"请求网页出错" message:msg];
}];
}
有时候会有这种类似的需求:
有若干个文件(a, b, c, d...),将文件上传到服务器后,返回一个
fileId
需要对这个fileId
做一些关联处理,并且上传成功后删除本地的该文件 每个文件上传互不影响,因此只对上传成功的文件做处理,上传失败的等待下一次机会接着上传
这种情况使用 SGSBatchRequest
就很容易实现
- (void)uploadDatas:(NSArray<NSData *> *)datas {
NSMutableArray *uploads = [NSMutableArray arrayWithCapacity:datas.count];
for (NSData *data in datas) {
UploadFileAPI *upload = [UploadFileAPI new];
// 拼接上传的数据
[upload setConstructingBodyBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFormData:data name:@"image"];
}];
// 设置回调
[upload setCompletionBlockWithSuccess:^(__kindof SGSBaseRequest * _Nonnull request) {
// 上传成功,拿到fileId进行关联处理
NSString *fileId = request.responseString;
// 删除文件...
} failure:^(__kindof SGSBaseRequest * _Nonnull request) {
// 上传失败处理
}];
// 将上传请求添加到数组中
[uploads addObject:upload];
}
// 构建批处理
SGSBatchRequest *batch = [[SGSBatchRequest alloc] initWithRequestArray:uploads];
batch.stopRequestWhenOneOfBatchFails = NO; // 其中某个请求失败时,不会停止其他请求,该属性默认为YES
[batch startWithCompletionBlock:^(SGSBatchRequest * _Nonnull batchRequest) {
// 所有请求全部处理完
} failure:nil];
// 由于 batch.stopRequestWhenOneOfBatchFails 为 NO,所以该 failure 分支不会走,可以置为 nil
}
当需要发起相互依赖的链式网络请求时,可以使用 SGSChainRequest
类。SGSChainRequest
先发起 A 请求,待 A 请求成功后再发起 B 请求,待 B 请求成功后再发起 C 请求,依次进行,直至所有请求全部响应成功。如果途中某个请求失败(例如 B 请求),那么后面的请求将会取消(例如 C 请求),并且通过 SGSChainRequest
的代理方法回调
例如某个场景下,需要用户先进行登录操作,待登录成功后获取到用户的信息,根据用户的信息在主页上加载用户的日程数据;如果登录失败则不做后面的处理
这种情况就需要发起具有依赖性的网络请求,通过 SGSChainRequest
类可以方便地实现这些功能
- (void)loginWithUsername:(NSString *)username password:(NSString *)password {
// 创建登录请求
LoginAPI *login = [[LoginAPI alloc] initWithUsername:username password:password];
// 创建链式请求
SGSChainRequest *chain = [[SGSChainRequest alloc] init];
// 添加登录依赖请求
[chain addRequest:login callback:^(SGSChainRequest * _Nonnull chainRequest, __kindof SGSBaseRequest * _Nonnull baseRequest) {
// 登录成功后获取 userId
NSString *userId = ((LoginAPI *)baseRequest).userId;
// 创建日程请求
ScheduleAPI *scheduleRequest = [[ScheduleAPI alloc] initWithUserId:userId];
// 添加日程请求
// 最终的请求可以通过 SGSChainRequest 的代理方法回调
// 因此可以不设置callback,
[chainRequest addRequest:scheduleRequest callback:nil];
}];
// 设置代理并发起请求
chain.delegate = self;
[chain start];
}
#pragma mark - SGSChainRequestDelegate
// 链式请求完毕
- (void)chainRequestFinished:(SGSChainRequest *)chainRequest {
id result = chainRequest.requestQueue.lastObject.responseObject;
if ([result isKindOfClass:[ScheduleGroup class]]) {
_textView.text = [NSString stringWithFormat:@"日程:\n%@\n", [result description]];
} else {
_textView.text = @"日程数据有误";
}
}
// 链式请求失败
- (void)chainRequestFailed:(SGSChainRequest *)chainRequest failedBaseRequest:(__kindof SGSBaseRequest *)request {
_failedCount++;
if (_failedCount >= 3) {
// 失败太多次不再继续请求
[self showAlert:@"失败了太多次,请重新登录..." message:nil];
_failedCount = 0;
return ;
}
if ([request isKindOfClass:[LoginAPI class]]) {
_textView.text = [NSString stringWithFormat:@"失败%ld次\n获取userId失败,重新请求中...\n", _failedCount];
} else {
_textView.text = [NSString stringWithFormat:@"失败%ld次\n获取日程失败,重新请求中...\n", _failedCount];
}
// 会从上一次失败的节点再次发起请求
[chainRequest start];
}
移动支撑平台 是研发中心移动团队打造的一套移动端开发便捷技术框架。这套框架立旨于满足公司各部门不同的移动业务研发需求,实现App快速定制的研发目标,降低研发成本,缩短开发周期,达到代码的易扩展、易维护、可复用的目的,从而让开发人员更专注于产品或项目的优化与功能扩展
整体框架采用组件化方式封装,以面向服务的架构形式供开发人员使用。同时兼容 Android 和 iOS 两大移动平台,涵盖 网络通信, 数据持久化存储, 数据安全, 移动ArcGIS 等功能模块(近期推出混合开发组件,只需采用前端的开发模式即可同时在 Android 和 iOS 两个平台运行),各模块间相互独立,开发人员可根据项目需求使用相应的组件模块
更多组件请参考:
如果您对移动支撑平台有更多的意见和建议,欢迎联系我们!
研发中心移动团队
2016 年 08月 26日
Lee, [email protected]
SGSHTTPModule is available under the MIT license. See the LICENSE file for more info.