更新时间: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;
} 父主题: 下载对象