iOS阿里云多图片上传
2019-10-21 本文已影响0人
HCL黄
前提,阿里云文件上传文档已经了解过
我们在上传多图片的时候,通常会遇到如下问题:
- 1、图片如何有序的上传?
- 2、如何保证所有图片全部上传完毕,这样才可以继续其他操作?
很多人可能会说开启同步任务,这样就可以保证有序并且也能保证所有图片都上传完毕了,但是同步任务会阻塞当前线程,这样可能会诱发不确定因素。
所以我接下去要讲的是利用异步任务来进行多图片上传
第一步,先初始化阿里云OSS
#import <UIKit/UIKit.h>
#import "OSSService.h"
@interface HttpTool : NSObject
// 单例
+ (HttpTool *)sharedInstance;
// 初始化OSS
+ (void)initOSSClient:(NSString *)tokenUrl;
@property (nonatomic, strong) OSSClient *client;
@end
#import "HttpTool.h"
@implementation HttpTool
+ (HttpTool *)sharedInstance{
// 设nil
static HttpTool *_sharedInstance = nil;
// 只执行一次
static dispatch_once_t oncePredicate;
// 初始化
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[HttpTool alloc] init];
});
return _sharedInstance;
}
#pragma mark - 初始化oss
+ (void)initOSSClient:(NSString *)tokenUrl {
// [OSSLog enableLog];
id<OSSCredentialProvider> credential = [[OSSFederationCredentialProvider alloc] initWithFederationTokenGetter:^OSSFederationToken * {
NSURL *url = [NSURL URLWithString:tokenUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
OSSTaskCompletionSource * tcs = [OSSTaskCompletionSource taskCompletionSource];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask * sessionTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error) {
[tcs setError:error];
return;
}
[tcs setResult:data];
}];
[sessionTask resume];
[tcs.task waitUntilFinished];
if (tcs.task.error) {
NSLog(@"get token error: %@", tcs.task.error);
return nil;
} else {
NSDictionary *object = [NSJSONSerialization JSONObjectWithData:tcs.task.result
options:kNilOptions
error:nil];
OSSFederationToken *token = [OSSFederationToken new];
token.tAccessKey = [object objectForKey:@"AccessKeyId"];
token.tSecretKey = [object objectForKey:@"AccessKeySecret"];
token.tToken = [object objectForKey:@"SecurityToken"];
token.expirationTimeInGMTFormat = [object objectForKey:@"Expiration"];
NSLog(@"get token: %@", token);
return token;
}
}];
OSSClientConfiguration *conf = [OSSClientConfiguration new];
conf.maxRetryCount = 3; // 网络请求遇到异常失败后的重试次数
conf.timeoutIntervalForRequest = 20; // 网络请求的超时时间
conf.timeoutIntervalForResource = 24*60*60; // 允许资源传输的最长时间
conf.maxConcurrentRequestCount = 30;
[HttpTool sharedInstance].client = [[OSSClient alloc] initWithEndpoint:OSS_endPoint credentialProvider:credential clientConfiguration:conf];
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化oss
[HttpTool initOSSClient:OSS_FederationToken_video];
}
第二步,我们来弄个任务类,专门处理阿里云多图片上传
#import <UIKit/UIKit.h>
#import "OSSService.h"
typedef void (^OSSCallBackBlock)(OSSTask * _Nullable task);
NS_ASSUME_NONNULL_BEGIN
@interface LAHttpTask : NSObject
/** 从内存中的NSData上传请求 */
@property (nonatomic, strong) OSSPutObjectRequest *dataPut;
/** 从文件上传时请求 */
@property (nonatomic, strong) OSSPutObjectRequest *filePut;
/**
bucketName: 存储空间的名字
objectKey: 文件夹路径
*/
- (void)startUploadImageData:(NSData *)imageData imaegW:(CGFloat)imageW imageH:(CGFloat)imageH bucketName:(NSString *)bucketName objectKey:(NSString *)objectKey callBack:(OSSCallBackBlock)callBack;
/**
fileURL: 文件路径url
*/
- (void)startUploadFileURL:(NSURL *)fileURL bucketName:(NSString *)bucketName objectKey:(NSString *)objectKey callBack:(OSSCallBackBlock)callBack;
@end
NS_ASSUME_NONNULL_END
#import "LAHttpTask.h"
@interface LAHttpTask ()
@end
@implementation LAHttpTask
/**
bucketName: 存储空间的名字
objectKey: 文件夹路径
*/
- (void)startUploadImageData:(NSData *)imageData imaegW:(CGFloat)imageW imageH:(CGFloat)imageH bucketName:(NSString *)bucketName objectKey:(NSString *)objectKey callBack:(OSSCallBackBlock)callBack {
self.dataPut = [OSSPutObjectRequest new];
self.dataPut.bucketName = bucketName;
self.dataPut.objectKey = objectKey;
self.dataPut.uploadingData = imageData;
self.dataPut.contentType = @"";
self.dataPut.contentMd5 = @"";
self.dataPut.contentEncoding = OSS_endPoint;
self.dataPut.contentDisposition = @"";
self.dataPut.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
// NSLog(@"%lld, %lld, %lld", bytesSent, totalByteSent, totalBytesExpectedToSend);
};
OSSTask * putTask = [[HttpTool sharedInstance].client putObject:self.dataPut];
[putTask continueWithBlock:^id(OSSTask *task) {
if (callBack) {
callBack(task);
}
return nil;
}];
}
- (void)startUploadFileURL:(NSURL *)fileURL bucketName:(NSString *)bucketName objectKey:(NSString *)objectKey callBack:(OSSCallBackBlock)callBack {
self.filePut = [OSSPutObjectRequest new];
// required fields
self.filePut.bucketName = bucketName;
self.filePut.objectKey = objectKey;
self.filePut.uploadingFileURL = fileURL;
// optional fields
self.filePut.contentType = @"";
self.filePut.contentMd5 = @"";
self.filePut.contentEncoding = OSS_endPoint;
self.filePut.contentDisposition = @"";
OSSTask * putTask = [[HttpTool sharedInstance].client putObject:self.filePut];
[putTask continueWithBlock:^id(OSSTask *task) {
if (callBack) {
callBack(task);
}
return nil;
}];
}
@end
第三步,我们来弄个任务管理器,专门处理每个图片对应每个LAHttpTask
@interface LAMissionManager : NSObject
/**
images: 图片数组
folderPath: 文件路径,如:video/subVideo/
urlPre: url前缀,如:OSS_VideoPre
bucketName: 磁盘名称,如:OSS_BucketName_video
*/
- (void)startMissionWithImages:(NSArray<UIImage *> *)images folderPath:(NSString *)folderPath urlPre:(NSString *)urlPre bucketName:(NSString *)bucketName callback:(void(^)(BOOL success,NSArray *imageUrls))callback;
@end
typedef void(^Callback)(BOOL success,NSArray *imageUrls);
@interface LAMissionManager ()
/**超时定时器*/
@property (nonatomic,strong) NSTimer *timer;
/** 保存传过来的图片数组 */
@property (nonatomic,strong) NSArray *images;
/** 完成回调 */
@property (nonatomic,strong) Callback callback;
/** 任务数组 */
@property (nonatomic,strong) NSMutableArray *imageTaskM;
/** 上传成功之后的图片链接数组 */
@property (nonatomic,strong) NSMutableArray *uploadSucceedImageM;
@end
@implementation LAMissionManager
/**
images: 图片数组
folderPath: 文件路径,如:video/subVideo/
urlPre: url前缀,如:OSS_VideoPre
bucketName: 磁盘名称,如:OSS_BucketName_video
*/
- (void)startMissionWithImages:(NSArray<UIImage *> *)images folderPath:(NSString *)folderPath urlPre:(NSString *)urlPre bucketName:(NSString *)bucketName callback:(void(^)(BOOL success,NSArray *imageUrls))callback {
self.images = images;
self.callback = callback;
//开启超时定时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:100 target:self selector:@selector(timeoutAction:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
// 清空数据
[self.uploadSucceedImageM removeAllObjects];
// 遍历图片数组
for (NSInteger i = 0; i < self.images.count; i++) {
UIImage *image = self.images[i];
// 创建阿里云上传任务,一个图片对应一个任务
LAHttpTask *task = [[LAHttpTask alloc] init];
// 添加到图片任务数组
[self.imageTaskM addObject:task];
CGFloat imaegW = image.size.width;
CGFloat imaegH = image.size.height;
// 图片命名
NSString *fileName = [NSString stringWithFormat:@"%@.png", [Tools_F deviceUUID]];
// 压缩图片
image = [GGUtil compressImage:image];
NSData *imageData = UIImagePNGRepresentation(image);
// 上传文件时,如果把 ObjectKey 写为"folder/subfolder/file",即是模拟了把文件上传到folder/subfolder/下的file文件。注意,路径默认是根目录,不需要以正斜线 / 开头。
NSString *objectKey = [NSString stringWithFormat:@"%@%@%@",folderPath,[Tools_F getCurrentUploadYMDHMS],fileName];
// OSS任务回调block
OSSCallBackBlock takCallBack = ^(OSSTask *task) {
if (!task.error) {
NSLog(@"upload success! image");
// 拼接完整图片链接
NSString *urlStr = [NSString stringWithFormat:@"%@%@",urlPre,objectKey];
// 拼接图片宽高及排序参数,宽高参数是为了后续后台返回图片url的时候我们可以直接拿到对应的宽高,sort参数是为了有序的上传给我们自己的后台
urlStr = [NSString stringWithFormat:@"%@?iW=%.f&iH=%.f?sort=%ld",urlStr,imaegW,imaegH,(long)i];
// 添加到上传成功的数组
[self.uploadSucceedImageM addObject:urlStr];
// 当uploadSucceedImageM与images相等时,说明所有图片上传完毕
if (self.uploadSucceedImageM.count == self.images.count) {
//停止定时器
[self.timer invalidate];
// 标志上传完毕,并且对已上传的图片链接进行排序处理
[self complete:YES imgUrls:self.uploadSucceedImageM];
}
}
else {
NSLog(@"upload failed image, error: %@" , task.error);
}
};
// 开启上传图片任务
[task startUploadImageData:imageData imaegW:imaegW imageH:imaegH bucketName:bucketName objectKey:objectKey callBack:takCallBack];
}
}
- (void)complete:(BOOL)isSuccess imgUrls:(NSMutableArray *)imgUrls {
if (self.callback) {
if (imgUrls.count == 0) {
// 没有图片链接,直接标志失败,返回
self.callback(NO, imgUrls);
return;
}
NSLog(@"complete imgUrls = %@",imgUrls);
NSMutableArray *imgIndexM = [NSMutableArray array];
NSMutableDictionary *imgDicM = [NSMutableDictionary dictionary];
// 遍历图片链接数组,这里是无序,因为上面我们用的是异步上传
for (NSInteger i = 0; i < imgUrls.count; i++) {
NSString *urlString = imgUrls[i];
// 取出的图片链接是否包含 ?sort=
if ([urlString containsString:@"?sort="]) {
// firstObjc = @“https://123.png”;
NSString *firstObjc = [[urlString componentsSeparatedByString:@"?sort="] firstObject];
// lastObjc = @“0”;
NSString *lastObjc = [[urlString componentsSeparatedByString:@"?sort="] lastObject];
[imgIndexM addObject:@(lastObjc.integerValue)]; // [2,5,1,3,4]
[imgDicM setObject:firstObjc forKey:lastObjc]; // {@"2": @"https://123.png"}
}else{ // 没有包含就直接随便加个值,注意不能在imgUrls范围内取值
[imgIndexM addObject:@(1000)];
[imgDicM setObject:urlString forKey:@(1000).stringValue]; // {@"1000": @"https://123.png"}
}
}
// 对存储sort值的imgIndexM进行排序
NSArray *imgResult = [imgIndexM sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}];
NSMutableArray *imgResultUrlM = [NSMutableArray array];
/**
imgDicM:[
{@"3":@"https://3.png"},
{@"4":@"https://4.png"},
{@"2":@"https://2.png"},
]
imgResult: [@"2",@"3",@"4"]
通过正确的排序,去取imgDicM对应key的值,然后存储
*/
for (NSNumber *resultIndex in imgResult) {
if (resultIndex.integerValue == 1000) {
[imgResultUrlM addObject:[imgDicM valueForKey:@(1000).stringValue]];
}else{
[imgResultUrlM addObject:[imgDicM valueForKey:[resultIndex stringValue]]];
}
}
self.callback(YES, imgResultUrlM);
}
}
第四步,使用TZImagePickerController
进行本地图片选择,具体使用大家可以自己去了解https://github.com/banchichen/TZImagePickerController
/** 多选视频或图片完成时调用 */
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray<UIImage *> *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto {
NSLog(@"***************多选视频或图片完成时调用*****************");
// 获取assets原图
[self getOriginalPhotoWithScript:assets];
}
- (void)getOriginalPhotoWithScript:(NSArray *)assets {
/**
以下代码是项目需求,排序传值到H5
当然也可以不用这么麻烦,直接遍历assets,拿到原图,存到数组就可以了,
*/
NSMutableDictionary *imgDicM = [NSMutableDictionary dictionary];
// 创建一个线程组
dispatch_group_t group = dispatch_group_create();
// 获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 这是一个HUD
[MBProgressHUD showMessage:@"导入图片中..." toView:kWindow];
// 遍历所有assets
for (NSInteger i = 0; i < assets.count; i++) {
PHAsset *asset = assets[i];
// 任务开始加入group
dispatch_group_enter(group);
// 开启异步任务
dispatch_async(queue, ^{
// 获取原图
[[TZImageManager manager] getOriginalPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info) {
if (!isDegraded) { // 原图
// 任务完成,移除
dispatch_group_leave(group);
if (photo) {
// 这里我使用的是对象模型来处理image,当然你也可以直接使用image
LAImageModel *model = [[LAImageModel alloc] init];
model.image = photo;
// 将对象模型作为值,下标i字符串作为key,存储到imgDicM
// 至于为什么要这么存储,当然是下面会用到啦
[imgDicM setObject:model forKey:@(i).stringValue];
}
}
}];
});
}
// 当全部任务完成之后,就会调用下面的方法
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
NSLog(@"imgDicM = %@",imgDicM);
// 隐藏HUD
[MBProgressHUD hideHUD];
// 初始化图片下标的数组
NSMutableArray *imgIndexM = [NSMutableArray array];
// 遍历imgDicM
[imgDicM enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL * _Nonnull stop) {
// 将imgDicM的key取出来,注意这里key是无序的
[imgIndexM addObject:@(key.integerValue)];
}];
// 接下来我们先对图片下标进行排序
NSArray *imgSortResults = [imgIndexM sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}];
// 重新整合有序的数据
for (NSNumber *resultIndex in imgSortResults) {
LAImageModel *model = [imgDicM valueForKey:[resultIndex stringValue]];
// 将来要提交给后台的
[self.exportScriptImgeM addObject:model.image];
}
NSLog(@"exportScriptImgeM = %@",self.exportScriptImgeM);
});
}
第五步,点击提交上传
- (void)scriptSubmitAction {
if (self.exportScriptImgeM.count == 0) {
[self showText:@"您还没有选择图片"];
return;
}
// 处理日期
NSArray *dates = [self dealWithDate];
[MBProgressHUD showMessage:@"提交中..."];
@weakify(self);
[self.manager startMissionWithImages:self.exportScriptImgeM callback:^(BOOL success, NSArray * _Nonnull imageUrls) {
@strongify(self);
NSLog(@"[x] imageUrls = %@",imageUrls);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[MBProgressHUD hideHUD];
}];
if (success) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self showText:@"上传成功"];
// 接下来就可以发给自己的后台了
。。。
}];
} else {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self showText:@"上传失败"];
}];
}
}];
}