解决方案

iOS 批量上传图片的 3 种方法

seo靠我 2023-09-23 07:56:14

AFNetworking 在去年年底升级到了 3.0。这个版本更新想必有很多好处,然而让我吃惊的是,它并没有 batch request 接口。之前的 1.x 版本、2.x 版本都实现了这个很常见的需SEO靠我求,不知道作者为何选择在 3.x 中去掉它。

在 AFNetworking 2 中,我们只需一行代码就能解决批量上传的问题:

[AFURLConnectionOperation batchOfRequesSEO靠我tOperations:operations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberSEO靠我OfOperations) {NSLog(@"%lu 上传完成,共 %lu", (long)numberOfFinishedOperations, (long)totalNumberOfOperatiSEO靠我ons); } completionBlock:^(NSArray *operations) {NSLog(@"上传完毕"); }];

但 AFNetworking 3 SEO靠我用的是NSURLSession,而不是用NSOperation来包装NSURLConnection,所以把整个AFURLConnectionOperation类都干掉了。上面的方法不能再用,并且也没有SEO靠我给出替代品。因此,我们只能自己动手了。

实现这个功能,有几个要点:

异步上传。批量请求里的每个请求都应该在不同线程,可以同时上传。在所有请求都完成之后,再通知回调。尽管异步请求的返回先后顺序没有一定,很可SEO靠我能后发出的请求先返回;但是最后回调的时候,请求返回的结果必须要按请求发出的顺序排列。比如,一个很常见的处理是,上传图片的接口返回该图片的 url;那么回调结果里的 url 顺序显然需要跟上传的图片顺序SEO靠我一一对应上。最好传完每张图片也能有一个回调,方便我们告诉用户上传的进度。

同时满足以上要点,主要有3种思路:GCD、NSOperation 以及 promise。这个需求也是示例多线程用法的一个很好的例SEO靠我子,所以我写了这篇比较详细的文章供大家参考。

下面的代码以图片上传为例。测试数据配置了 3 张图片,其中第 2 张图片尺寸最小,往往先上传完毕,用来测试请求发出顺序与返回顺序不一致的情况。 

方法一:GCDSEO靠我 dispatch group

我们知道,GCD dispatch 是多线程处理最简单的方法。全部请求完成后再通知回调的需求,很适合利用 dispatch group 来完成。至于保证返回结果的顺序,我SEO靠我们只好自己来做了。

首先需要一个方法,对于每张图片生成一个上传请求。

- (NSURLSessionUploadTask*)uploadTaskWithImage:(UIImage*)image compSEO靠我letion:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionBlock {// 构造 SEO靠我NSURLRequestNSError* error = NULL;NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializeSEO靠我r] multipartFormRequestWithMethod:@"POST" URLString:[self uploadUrl] parameters:nil constructingBodySEO靠我WithBlock:^(id<AFMultipartFormData> formData) {NSData* imageData = UIImageJPEGRepresentation(image, SEO靠我1.0);[formData appendPartWithFileData:imageData name:@"file" fileName:@"someFileName" mimeType:@"mulSEO靠我tipart/form-data"];} error:&error];// 可在此处配置验证信息// 将 NSURLRequest 与 completionBlock 包装为 NSURLSessionSEO靠我UploadTaskAFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[SEO靠我NSURLSessionConfiguration defaultSessionConfiguration]];NSURLSessionUploadTask *uploadTask = [manageSEO靠我r uploadTaskWithStreamedRequest:request progress:^(NSProgress * _Nonnull uploadProgress) {} completiSEO靠我onHandler:completionBlock];return uploadTask; }

在这个方法里,我们首先用UIImageJPEGRepresentation把UIImageSEO靠我变为NSData。然后用AFHTTPRequestSerializer来生成NSMutableURLRequest,[self uploadUrl]是上传接口的地址。为安全考虑,一般上传的接口都有身份SEO靠我验证的需求,比如在请求 header 中加入 auth 信息,可以在此配置NSMutableURLRequest的 header。最后,我们用 AFURLSessionManager把 NSURLReSEO靠我quest和回调 block 包装成一个NSURLSessionUploadTask。

有了生成请求的方法,批量发出请求的方法如下:

- (IBAction)runDispatchTest:(id)senSEO靠我der {// 需要上传的数据NSArray* images = [self images];// 准备保存结果的数组,元素个数与上传的图片个数相同,先用 NSNull 占位NSMutableArraSEO靠我y* result = [NSMutableArray array];for (UIImage* image in images) {[result addObject:[NSNull null]];SEO靠我}dispatch_group_t group = dispatch_group_create();for (NSInteger i = 0; i < images.count; i++) {dispSEO靠我atch_group_enter(group);NSURLSessionUploadTask* uploadTask = [self uploadTaskWithImage:images[i] comSEO靠我pletion:^(NSURLResponse *response, NSDictionary* responseObject, NSError *error) {if (error) {NSLog(SEO靠我@"第 %d 张图片上传失败: %@", (int)i + 1, error);dispatch_group_leave(group);} else {NSLog(@"第 %d 张图片上传成功: %@SEO靠我", (int)i + 1, responseObject);@synchronized (result) { // NSMutableArray 是线程不安全的,所以加个同步锁result[i] =SEO靠我 responseObject;}dispatch_group_leave(group);}}];[uploadTask resume];}dispatch_group_notify(group, dSEO靠我ispatch_get_main_queue(), ^{NSLog(@"上传完成!");for (id response in result) {NSLog(@"%@", response);}});SEO靠我 }

可以看到,我们把所有请求放在一个 dispatch_group 里。首先用dispatch_group_create()来创建这个 group。然后,对于每一个 uploadTasSEO靠我k,在创建之前先执行dispatch_group_enter(group),在结束回调的 block里执行dispatch_group_leave(group)。结束回调的代码放在dispatch_gSEO靠我roup_notify里即可。

实际执行中,首先是所有 task 都进入 group,同时开始上传;上传完成之后依次离开 group;最后 group 空了会自动调用传入group_notify的回调,SEO靠我整个过程完成。

那么如何把回调数据排成正确的顺序呢?借助 block 会保存自动变量的特点,我们让每个 task 的回调 block 都自动带上标志请求次序的变量 i,只需把返回结果填入数组的第 i 位SEO靠我即可。所以在开始请求之前,先创建好保存返回结果的数组,元素个数与请求个数相等,每个位置上用[NSNull null]占位。每个请求返回之后,把自己那个位置上的[NSNull null]替换成返回结果。SEO靠我全部请求返回之后,数组里保存的自然是按请求顺序排列的回调数据。

这里注意,因为 NSMutableArray 是线程不安全的,而每个请求返回时是在不同线程操作同一个数组,所以我用@synchronizeSEO靠我d把操作数组的代码锁住了,锁的对象就用这个数组即可。这样保证所有线程执行到这一句都得串行,避免线程安全问题。

一次测试结果如下:

2016-05-13 15:49:43.042 HAMAFNetworkiSEO靠我ngBatchRequestDemo[23102:5717076] 第 2 张图片上传成功: {imageBucket = test;imageKey = "331eb245-741f-4fdc-87SEO靠我69-fdfb9e646da7";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/331eb245-741f-4fdc-8769-fdfb9e646SEO靠我da7?imageMogr2/thumbnail/640x"; } 2016-05-13 15:49:43.098 HAMAFNetworkingBatchRequesSEO靠我tDemo[23102:5717076] 第 1 张图片上传成功: {imageBucket = test;imageKey = "d08f5370-c8b6-4912-b4e5-c73ea31346SEO靠我37";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/d08f5370-c8b6-4912-b4e5-c73ea3134637?imageMogrSEO靠我2/thumbnail/640x"; } 2016-05-13 15:49:43.120 HAMAFNetworkingBatchRequestDemo[23102:5SEO靠我717076] 第 3 张图片上传成功: {imageBucket = test;imageKey = "bdf13097-8128-4f04-bcbc-462bd2a728ab";imageUrl SEO靠我= "http://7xnpgs.com1.z0.glb.clouddn.com/bdf13097-8128-4f04-bcbc-462bd2a728ab?imageMogr2/thumbnail/6SEO靠我40x"; } 2016-05-13 15:49:43.120 HAMAFNetworkingBatchRequestDemo[23102:5717076] 上传完成!SEO靠我 2016-05-13 15:49:43.121 HAMAFNetworkingBatchRequestDemo[23102:5717076] {imageBucket = test;SEO靠我imageKey = "d08f5370-c8b6-4912-b4e5-c73ea3134637";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/SEO靠我d08f5370-c8b6-4912-b4e5-c73ea3134637?imageMogr2/thumbnail/640x"; } 2016-05-13 15:49:SEO靠我43.121 HAMAFNetworkingBatchRequestDemo[23102:5717076] {imageBucket = test;imageKey = "331eb245-741f-SEO靠我4fdc-8769-fdfb9e646da7";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/331eb245-741f-4fdc-8769-fdSEO靠我fb9e646da7?imageMogr2/thumbnail/640x"; } 2016-05-13 15:49:43.124 HAMAFNetworkingBatcSEO靠我hRequestDemo[23102:5717076] {imageBucket = test;imageKey = "bdf13097-8128-4f04-bcbc-462bd2a728ab";imSEO靠我ageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/bdf13097-8128-4f04-bcbc-462bd2a728ab?imageMogr2/thumSEO靠我bnail/640x"; }

可以看到,尽管第 2 张图片尺寸最小、最先传完,第 1 张图片后传完,但最后的结果顺序还是正确的。

方法二:NSOperationQueue

注意:这个方法有点SEO靠我问题,出在用 KVO 监听 task 的 state 的部分:看 AFN 源码可以看到它是在 task 的 didComplete 的 delegate 方法里执行 completionHandlerSEO靠我 的,此时 task 的 state 已经变成 Completed。所以 KVO 有可能会略先一点执行,此时最后一个请求的 success block 可能还没执行。加一点点延时应该能解决问题……但这SEO靠我样不太严谨。我再想想有没有更合适的监听的东西。非常感谢王银博的 demo,帮我发现这个问题~

能用 dispatch 实现的功能,自然也可以用NSOperationQueue。NSOperation 这SEO靠我一套比 dispatch 写起来要麻烦一些,不过有几个优点:

NSOperation是对象,不像 dispatch 是 c 函数。这就意味着你可以继承它,可以给它加 category,在执行过程中也可以SEO靠我始终管理它,访问到它,查看它的状态等,不像 dispatch 是一撒手就够不着了。用NSOperation执行的任务,执行过程中可以随时取消。dispatch 一经发出是无法取消的。NSOperatiSEO靠我onQueue可以限制最大并发数。假如队列里真有 100 个文件要传,开出 100 个线程反而会严重影响性能。NSOperationQueue可以很方便地设置maxConcurrentOperatioSEO靠我nCount。dispatch 也可以限制最大并发数(参考苹果的文档)不过写起来麻烦很多。

就我们的需求而言,用 NSOperation 有一个很方便的特点:dispatch 里的任务各自为政,而NSOSEO靠我peration之前是可以有依赖关系的。我们就可以利用这一点,来发起所有任务上传完成后的回调:把这个完成回调也做成一个NSOperation,让这个NSOperation前置依赖所有上传的NSOperSEO靠我ation,这样等到所有上传的NSOperation完成之后,这个回调NSOperation才会开始执行。

然而,用NSOperation也有一个很不方便的特点:NSOperationQueue是用 KSEO靠我VO 观察NSOperation状态来判断任务是否已结束的。而我们请求用的NSURLSessionTask,它长得很像一个NSOperation,但却并不是NSOperation的子类。所以,这一套方SEO靠我法最麻烦的地方就在于我们需要写一个自定义的NSOperation子类,只是为了跟踪NSURLSessionTask的状态。

自定义的NSOperation代码如下:

HAMURLSessionWrappeSEO靠我rOperation.h#import <Foundation/Foundation.h>@interface HAMURLSessionWrapperOperation : NSOperation+SEO靠我 (instancetype)operationWithURLSessionTask:(NSURLSessionTask*)task;@end

HAMURLSessionWrapperOperationSEO靠我.m

#import "HAMURLSessionWrapperOperation.h"@interface HAMURLSessionWrapperOperation () {BOOL executiSEO靠我ng; // 系统的 finished 是只读的,不能修改,所以只能重写一个。BOOL finished; }@property (nonatomic, strong) NSURLSeSEO靠我ssionTask* task;@property (nonatomic, assign) BOOL isObserving;@end@implementation HAMURLSessionWrapSEO靠我perOperation#pragma mark - Observe Task+ (instancetype)operationWithURLSessionTask:(NSURLSessionTaskSEO靠我*)task {HAMURLSessionWrapperOperation* operation = [HAMURLSessionWrapperOperation new];operation.tasSEO靠我k = task;return operation; }- (void)dealloc {[self stopObservingTask]; }- (void)starSEO靠我tObservingTask {@synchronized (self) {if (_isObserving) {return;}[_task addObserver:self forKeyPath:SEO靠我@"state" options:NSKeyValueObservingOptionNew context:nil];_isObserving = YES;} }- (void)stoSEO靠我pObservingTask { // 因为要在 dealloc 调,所以用下划线不用点语法@synchronized (self) {if (!_isObserving) {return;}_isOSEO靠我bserving = NO;[_task removeObserver:self forKeyPath:@"state"];} }- (void)observeValueForKeyPSEO靠我ath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(voSEO靠我id *)context {if (self.task.state == NSURLSessionTaskStateCanceling || self.task.state == NSURLSessiSEO靠我onTaskStateCompleted) {[self stopObservingTask];[self completeOperation];} }#pragma mark - NSEO靠我SOperation methods- (void)start {// Always check for cancellation before launching the task.if ([selSEO靠我f isCancelled]){// Must move the operation to the finished state if it is canceled.[self willChangeVSEO靠我alueForKey:@"isFinished"];finished = YES;[self didChangeValueForKey:@"isFinished"];return;}// If theSEO靠我 operation is not canceled, begin executing the task.[self willChangeValueForKey:@"isExecuting"];[NSSEO靠我Thread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];executing = YES;[self dSEO靠我idChangeValueForKey:@"isExecuting"]; }- (void)main {@try {[self startObservingTask];[self.taSEO靠我sk resume];}@catch (NSException * e) {NSLog(@"Exception %@", e);} }- (void)completeOperationSEO靠我 {[self willChangeValueForKey:@"isFinished"];[self willChangeValueForKey:@"isExecuting"];executing =SEO靠我 NO;finished = YES;[self didChangeValueForKey:@"isExecuting"];[self didChangeValueForKey:@"isFinisheSEO靠我d"]; }- (BOOL)isAsynchronous {return YES; }- (BOOL)isExecuting {return executing; SEO靠我 }- (BOOL)isFinished {return finished; }@end

代码有点长,但没办法。我们的目标是对每个NSURLSessionTask都包装出一个HSEO靠我AMURLSessionWrapperOperation,这个NSOperation完全随着NSURLSessionTask的状态而动,在 Task 结束之后发出 KVO 的通知,通知NSOperatSEO靠我ionQueue这个任务结束。

系统NSOperation的finished属性是只读的,不能修改;为了记录值和发出 KVO 的通知,我们只能在旁再定义一个finished的成员变量,通过重写- (BOSEO靠我OL)isFinished等 getter 方法,盖掉原来的finished属性。现在几乎全用 property,这种成员变量的写法好久没看见过了,没想到还有这种用处,这种特殊的写法还是从苹果文档学来SEO靠我的(参考这里)。

这里 start 方法照抄苹果文档,在新线程调起 main 方法。main 方法里就两件事:开始 KVO 观察上传 task 的 state 属性,然后启动 task。一旦 task SEO靠我完成(或失败),接到 KVO 的通知,我们停止对 task 的观察,然后发出自己的 KVO 通知去通知NSOperationQueue。这里我们手动调起了[self willChangeValueFoSEO靠我rKey:@"isFinished"];和[self didChangeValueForKey:@"isFinished"];,又重写了- (BOOL)isFinished方法,就把只读的finishSEO靠我ed属性偷天换日变成我们自己定义的finished成员变量了。

自定义NSOperation说完了,下面我们来看看怎么使用这个类。我们同样要利用上面 dispatch 一节写的那个uploadTaskWSEO靠我ithImage:completion方法,根据图片生成请求。发出请求的代码如下:

- (IBAction)runNSOperationTest:(id)sender {// 需要上传的数据NSArraSEO靠我y* images = [self images];// 准备保存结果的数组,元素个数与上传的图片个数相同,先用 NSNull 占位NSMutableArray* result = [NSMutablSEO靠我eArray array];for (UIImage* image in images) {[result addObject:[NSNull null]];}NSOperationQueue *quSEO靠我eue = [[NSOperationQueue alloc] init];queue.maxConcurrentOperationCount = 5;NSBlockOperation *compleSEO靠我tionOperation = [NSBlockOperation blockOperationWithBlock:^{[[NSOperationQueue mainQueue] addOperatiSEO靠我onWithBlock:^{ // 回到主线程执行,方便更新 UI 等NSLog(@"上传完成!");for (id response in result) {NSLog(@"%@", responsSEO靠我e);}}];}];for (NSInteger i = 0; i < images.count; i++) {NSURLSessionUploadTask* uploadTask = [self uSEO靠我ploadTaskWithImage:images[i] completion:^(NSURLResponse *response, NSDictionary* responseObject, NSESEO靠我rror *error) {if (error) {NSLog(@"第 %d 张图片上传失败: %@", (int)i + 1, error);} else {NSLog(@"第 %d 张图片上传成功SEO靠我: %@", (int)i + 1, responseObject);@synchronized (result) { // NSMutableArray 是线程不安全的,所以加个同步锁result[SEO靠我i] = responseObject;}}}];HAMURLSessionWrapperOperation *uploadOperation = [HAMURLSessionWrapperOperaSEO靠我tion operationWithURLSessionTask:uploadTask];[completionOperation addDependency:uploadOperation];[quSEO靠我eue addOperation:uploadOperation];}[queue addOperation:completionOperation]; }

保持结果顺序的方法与 disSEO靠我patch 相同,都是我们自己完成的。我们把maxConcurrentOperationCount定成 5,避免并发过多竞争资源。先创建结束回调的 operation,再让它依赖后面创建的每一个上传 SEO靠我operation。因为一般回调都要涉及到更新 UI,所以让它回到主线程执行。后面根据每张图片逐一创建 task、包装成 operation。创建好之后,加进 operationQueue 里就开始跑SEO靠我了。

一次测试结果如下:

2016-05-13 15:50:06.269 HAMAFNetworkingBatchRequestDemo[23102:5717076] 第 2 张图片上传成功: {imagSEO靠我eBucket = test;imageKey = "cc60ab02-7745-4c60-8697-8bae1501768b";imageUrl = "http://7xnpgs.com1.z0.gSEO靠我lb.clouddn.com/cc60ab02-7745-4c60-8697-8bae1501768b?imageMogr2/thumbnail/640x"; } 20SEO靠我16-05-13 15:50:06.365 HAMAFNetworkingBatchRequestDemo[23102:5717076] 第 1 张图片上传成功: {imageBucket = tesSEO靠我t;imageKey = "ee9c1492-a8f1-441c-9bd4-c90756841266";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.coSEO靠我m/ee9c1492-a8f1-441c-9bd4-c90756841266?imageMogr2/thumbnail/640x"; } 2016-05-13 15:5SEO靠我0:06.413 HAMAFNetworkingBatchRequestDemo[23102:5717076] 第 3 张图片上传成功: {imageBucket = test;imageKey = SEO靠我"6fe8197a-4638-4706-afe1-3aca203cf73f";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/6fe8197a-46SEO靠我38-4706-afe1-3aca203cf73f?imageMogr2/thumbnail/640x"; } 2016-05-13 15:50:06.414 HAMASEO靠我FNetworkingBatchRequestDemo[23102:5717076] 上传完成! 2016-05-13 15:50:06.414 HAMAFNetworkingBatcSEO靠我hRequestDemo[23102:5717076] {imageBucket = test;imageKey = "ee9c1492-a8f1-441c-9bd4-c90756841266";imSEO靠我ageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/ee9c1492-a8f1-441c-9bd4-c90756841266?imageMogr2/thumSEO靠我bnail/640x"; } 2016-05-13 15:50:06.415 HAMAFNetworkingBatchRequestDemo[23102:5717076SEO靠我] {imageBucket = test;imageKey = "cc60ab02-7745-4c60-8697-8bae1501768b";imageUrl = "http://7xnpgs.coSEO靠我m1.z0.glb.clouddn.com/cc60ab02-7745-4c60-8697-8bae1501768b?imageMogr2/thumbnail/640x"; } SEO靠我 2016-05-13 15:50:06.415 HAMAFNetworkingBatchRequestDemo[23102:5717076] {imageBucket = test;imagSEO靠我eKey = "6fe8197a-4638-4706-afe1-3aca203cf73f";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/6fe8SEO靠我197a-4638-4706-afe1-3aca203cf73f?imageMogr2/thumbnail/640x"; }

结果也是正确的。

方法三:promise

上面的两种方法,我们都SEO靠我是自己用数组、占位、逐位替换的方法,自己写代码保证返回数据顺序正确的。其实这种需要多个线程执行、全部结束后回调、结果顺序保证正确的需求,一般最适合用 promise 来做。各个语言都有自己的 promSEO靠我ise 实现,iOS 也有好几种。这里我们试用一下 iOS 最著名的实现 PromiseKit。

在 github 上 5000 多个 star,这个 lib 是 Objective-C 、Swift SEO靠我通用的,两套代码都有。在网络请求方面,它要依赖同一个作者写的另一个库 OMGHTTPURLRQ,导入的时候小费周折。PromiseKit 这一套方法与 AFNetworking 库就没关系了,可能有些SEO靠我离题,但是用起来是最为方便的。

这里我们不再需要上面那个生成NSURLSessionTask的方法了,现在我们需要把NSURLRequest包装成AnyPromise:

- (AnyPromise *)uSEO靠我ploadPromiseWithImage:(UIImage *)image completion:(id (^)(id))completionBlock {NSString* url = [selfSEO靠我 uploadUrl];NSData* imageData = UIImageJPEGRepresentation(image, 1.0);OMGMultipartFormData *multiparSEO靠我tFormData = [OMGMultipartFormData new];[multipartFormData addFile:imageData parameterName:@"file" fiSEO靠我lename:@"someFileName" contentType:@"multipart/form-data"];NSMutableURLRequest* request = [OMGHTTPURSEO靠我LRQ POST:url :multipartFormData error:nil];// 可在此处配置验证信息if (completionBlock) {return [NSURLConnectioSEO靠我n promise:request].then(completionBlock);} else {return [NSURLConnection promise:request];} SEO靠我}

这里可以看到 promise 的.then语法。它是一个 C 函数,传进的参数是这项 promise 完成之后下一步需要执行的 block,返回值仍然是AnyPromise,所以可以一直.then(SEO靠我).then()……这样链式调用下去。我们在这里让它上传完单张图片之后执行单张图片的回调,把回调 block『附身』在上传的 promise 之后。

上面就是创建 promise 的过程。那么执行 prSEO靠我omise 的代码怎么写呢?

- (IBAction)runPromiseTest:(id)sender {// 需要上传的数据NSArray* images = [self images];NSMutSEO靠我ableArray* promises = [NSMutableArray array];for (NSInteger i = 0; i < images.count; i++) {UIImage* SEO靠我image = images[i];[promises addObject:[self uploadPromiseWithImage:image completion:^(id resultImageSEO靠我Url){NSLog(@"第 %d 张图片上传成功: %@", (int)i + 1, resultImageUrl);return resultImageUrl;}]];}PMKWhen(promiSEO靠我ses).then(^(NSArray *results) {NSLog(@"上传完成!");NSLog(@"%@", results);}).catch(^{NSLog(@"图片上传失败");});SEO靠我 }

可以看到代码非常简洁,可读性又好,比前两种方法都省去不少代码,这是 promise 的一大优势。我们只需把针对每张图片创建一个 promise ,放进一个 promises 数组,SEO靠我然后PMKWhen(promises).then()就能帮我们搞定一切了——是不是很神奇呢?每个任务单开线程、等待全部任务执行完、结果正确排序等诸多工序,全都由这一行代码搞定了。看看测试结果:

2016SEO靠我-05-13 15:30:45.447 HAMAFNetworkingBatchRequestDemo[23093:5713564] 第 2 张图片上传成功: {imageBucket = test;SEO靠我imageKey = "5d50cdd3-2272-4d3b-bbb1-054d1d08e682";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/SEO靠我5d50cdd3-2272-4d3b-bbb1-054d1d08e682?imageMogr2/thumbnail/640x"; } 2016-05-13 15:30:SEO靠我45.595 HAMAFNetworkingBatchRequestDemo[23093:5713564] 第 1 张图片上传成功: {imageBucket = test;imageKey = "fSEO靠我f3874d2-8477-4ceb-a49f-1938168b0456";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/ff3874d2-8477SEO靠我-4ceb-a49f-1938168b0456?imageMogr2/thumbnail/640x"; } 2016-05-13 15:30:46.127 HAMAFNSEO靠我etworkingBatchRequestDemo[23093:5713564] 第 3 张图片上传成功: {imageBucket = test;imageKey = "2b8b0175-1274-SEO靠我4de9-b809-7d88809ef606";imageUrl = "http://7xnpgs.com1.z0.glb.clouddn.com/2b8b0175-1274-4de9-b809-7dSEO靠我88809ef606?imageMogr2/thumbnail/640x"; } 2016-05-13 15:30:46.130 HAMAFNetworkingBatcSEO靠我hRequestDemo[23093:5713564] 上传完成! 2016-05-13 15:30:46.130 HAMAFNetworkingBatchRequestDemo[23SEO靠我093:5713564] ({imageBucket = test;imageKey = "ff3874d2-8477-4ceb-a49f-1938168b0456";imageUrl = "httpSEO靠我://7xnpgs.com1.z0.glb.clouddn.com/ff3874d2-8477-4ceb-a49f-1938168b0456?imageMogr2/thumbnail/640x";},SEO靠我{imageBucket = test;imageKey = "5d50cdd3-2272-4d3b-bbb1-054d1d08e682";imageUrl = "http://7xnpgs.com1SEO靠我.z0.glb.clouddn.com/5d50cdd3-2272-4d3b-bbb1-054d1d08e682?imageMogr2/thumbnail/640x";},{imageBucket =SEO靠我 test;imageKey = "2b8b0175-1274-4de9-b809-7d88809ef606";imageUrl = "http://7xnpgs.com1.z0.glb.clouddSEO靠我n.com/2b8b0175-1274-4de9-b809-7d88809ef606?imageMogr2/thumbnail/640x";} )

同样是正确的。

所以看起来用 promiSEO靠我se 还是非常方便的。不过这是我第一次尝试用它,还不知道在工程中实际应用会有什么缺点。

“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2