更新时间:2026-08-28 GMT+08:00
分享

批量下载

本示例用于批量下载OBS桶中的对象到本地目录。您可以通过列举满足前缀条件的对象,再以指定并发数并发下载,文件按原Key结构保存到本地目录,适用于批量导出、离线备份等场景。

#import <Foundation/Foundation.h>
#import <OBS/OBS.h>
#pragma mark - Result Model
@interface BatchDownloadResult : NSObject
@property (nonatomic, copy) NSString *objectKey;
@property (nonatomic, copy) NSString *localPath;
@property (nonatomic, assign) BOOL success;
@property (nonatomic, strong) NSError *error;
@property (nonatomic, copy) NSString *httpStatusCode;
@end
@implementation BatchDownloadResult
@end
#pragma mark - Batch Downloader
@interface BatchDownloader : NSObject
@property (nonatomic, strong) OBSClient *client;
@property (nonatomic, copy) NSString *bucketName;
@property (nonatomic, assign) NSInteger concurrentCount;
@property (nonatomic, strong) NSMutableArray<BatchDownloadResult *> *results;
@property (nonatomic, strong) dispatch_queue_t resultQueue;
- (instancetype)initWithClient:(OBSClient *)client
                    bucketName:(NSString *)bucketName
               concurrentCount:(NSInteger)concurrentCount;
- (void)downloadObjects:(NSArray<NSString *> *)objectKeys
          toDirectory:(NSString *)directoryPath
            waitUntilDone:(BOOL)wait;
@end
@implementation BatchDownloader
- (instancetype)initWithClient:(OBSClient *)client
                    bucketName:(NSString *)bucketName
               concurrentCount:(NSInteger)concurrentCount {
    self = [super init];
    if (self) {
        _client = client;
        _bucketName = bucketName;
        _concurrentCount = MIN(concurrentCount > 0 ? concurrentCount : 3, 10);
        _results = [NSMutableArray array];
        _resultQueue = dispatch_queue_create("com.obs.batchdownload.result", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}
- (void)downloadObjects:(NSArray<NSString *> *)objectKeys
          toDirectory:(NSString *)directoryPath
            waitUntilDone:(BOOL)wait {
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(self.concurrentCount);
    dispatch_queue_t queue = dispatch_queue_create("com.obs.batchdownload", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"[BatchDownload] Starting %lu downloads, concurrency: %ld",
          (unsigned long)objectKeys.count, (long)self.concurrentCount);
    for (NSString *objectKey in objectKeys) {
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC);
            if (dispatch_semaphore_wait(semaphore, timeout) != 0) {
                NSLog(@"[BatchDownload] semaphore wait timeout, skip object: %@", objectKey);
                dispatch_group_leave(group);
                return;
            }
            [self downloadObject:objectKey toDirectory:directoryPath completion:^{
                dispatch_semaphore_signal(semaphore);
                dispatch_group_leave(group);
            }];
        });
    }
    if (wait) {
        dispatch_time_t groupTimeout = dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC);
        if (dispatch_group_wait(group, groupTimeout) != 0) {
            NSLog(@"[BatchDownload] group wait timeout, some downloads may not finish");
        }
        [self printSummary];
    }
}
- (void)downloadObject:(NSString *)objectKey
          toDirectory:(NSString *)directoryPath
            completion:(void (^)(void))completion {
    NSError *dirError;    [[NSFileManager defaultManager] createDirectoryAtPath:directoryPath
                              withIntermediateDirectories:YES
                                               attributes:nil
                                                    error:&dirError];    if (dirError) {        NSLog(@"目录创建失败: %@", dirError);    }
    NSString *localPath = [directoryPath stringByAppendingPathComponent:objectKey];
    dirError = nil;    [[NSFileManager defaultManager] createDirectoryAtPath:[localPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:&dirError];    if (dirError) {        NSLog(@"目录创建失败: %@", dirError);    }
    OBSGetObjectToFileRequest *request = [[OBSGetObjectToFileRequest alloc] initWithBucketName:self.bucketName
                                                                                   objectKey:objectKey
                                                                           downloadFilePath:localPath];
    request.downloadProgressBlock = ^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
        float progress = totalBytesExpectedToWrite > 0 ? MIN((float)totalBytesWritten * 100.0f / (float)totalBytesExpectedToWrite, 100.0f) : 0;
        NSLog(@"[Download] %@ - %0.1f%% (%lld / %lld bytes)", objectKey, progress, totalBytesWritten, totalBytesExpectedToWrite);
    };
    // 注意:getObject不做对象存在性预检,对象不存在时服务端返回404
    __weak typeof(self) weakSelf = self;    __strong typeof(weakSelf) strongSelf = weakSelf;    if (!strongSelf) return;    [self.client getObject:request completionHandler:^(OBSGetObjectResponse *response, NSError *error) {
        BatchDownloadResult *result = [[BatchDownloadResult alloc] init];
        result.objectKey = objectKey;
        result.localPath = localPath;
        result.error = error;
        result.success = (error == nil);
        // 注意:记录HTTP状态码,statusCode为nil时用"-"占位
        result.httpStatusCode = response.statusCode ?: @"-";
        if (error) {
            // 注意:下载失败时建议向用户展示错误信息或提供重试机制
            NSLog(@"[Download] FAILED - %@ : %@", objectKey, error.localizedDescription);
        } else {
            NSLog(@"[Download] SUCCESS - %@ -> %@", objectKey, localPath);
        }
        dispatch_sync(strongSelf.resultQueue, ^{ [strongSelf.results addObject:result]; });
        if (completion) completion();
    }];
}
- (void)printSummary {
    NSInteger success = 0, failure = 0;
    for (BatchDownloadResult *r in self.results) {
        if (r.success) success++;
        else failure++;
    }
    NSLog(@"\n========== Batch Download Summary ==========");
    NSLog(@"Total: %lu | Success: %ld | Failed: %ld", (unsigned long)self.results.count, (long)success, (long)failure);
    if (failure > 0) {
        NSLog(@"Failed items:");
        for (BatchDownloadResult *r in self.results) {
            if (!r.success) {
                NSLog(@"  - %@ : %@", r.objectKey, r.error.localizedDescription);
            }
        }
    }
    NSLog(@"============================================\n");
}
@end
#pragma mark - Entry Point
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // === Configuration ===
        NSString *endPoint = @"https://your-endpoint";
        // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量AccessKeyID和SecretAccessKey。
        // 您可以登录访问管理控制台获取访问密钥AK/SK,获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
        char* ak_env = getenv("AccessKeyID");
        char* sk_env = getenv("SecretAccessKey");
        if (ak_env == NULL || sk_env == NULL) {
            NSLog(@"Error: AccessKeyID or SecretAccessKey environment variable not set");
            return 1;
        }
        NSString *AK = [NSString stringWithUTF8String:ak_env];
        NSString *SK = [NSString stringWithUTF8String:sk_env];
        NSString *bucketName = @"your-bucket-name";
        NSInteger concurrentCount = 3;  // Adjust as needed
        // Objects to download
        NSArray<NSString *> *objectKeys = @[
            @"folder/file1.txt",
            @"folder/file2.jpg",
            @"folder/file3.pdf",
            @"folder/file4.mp4",
            @"folder/file5.zip"
        ];
        // Local download directory
        NSString *downloadDir = [NSTemporaryDirectory() stringByAppendingPathComponent:@"BatchDownload"];
        // === Initialize OBS Client ===
        OBSStaticCredentialProvider *credentialProvider = [[OBSStaticCredentialProvider alloc] initWithAccessKey:AK secretKey:SK];
        OBSServiceConfiguration *conf = [[OBSServiceConfiguration alloc] initWithURLString:endPoint credentialProvider:credentialProvider];
        // Optionally increase concurrent download limit
        conf.maxConcurrentDownloadRequestCount = concurrentCount;
        OBSClient *client = [[OBSClient alloc] initWithConfiguration:conf];
        // === Batch Download ===
        BatchDownloader *downloader = [[BatchDownloader alloc] initWithClient:client
                                                                   bucketName:bucketName
                                                              concurrentCount:concurrentCount];
        // 注意:下载前建议检查本地磁盘空间是否充足
        [downloader downloadObjects:objectKeys toDirectory:downloadDir waitUntilDone:YES];
    }
    return 0;
}

相关文档