iOS阿里云多图片上传

2019-10-21  本文已影响0人  HCL黄

前提,阿里云文件上传文档已经了解过

我们在上传多图片的时候,通常会遇到如下问题:

很多人可能会说开启同步任务,这样就可以保证有序并且也能保证所有图片都上传完毕了,但是同步任务会阻塞当前线程,这样可能会诱发不确定因素。
所以我接下去要讲的是利用异步任务来进行多图片上传

第一步,先初始化阿里云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:@"上传失败"];
            }];
        }
    }];
}
上一篇下一篇

猜你喜欢

热点阅读