更新时间:2026-08-28 GMT+08:00
批量复制对象
本示例用于批量复制OBS中的对象,支持同桶复制和跨桶复制。您可以指定源对象Key和目标对象Key的对应关系,以指定并发数并发执行复制操作,并可选保留源对象元数据或替换为新元数据,适用于数据迁移、跨区域备份等场景。
#import <Foundation/Foundation.h>
#import <OBS/OBS.h>
#pragma mark - Result Model
@interface BatchCopyResult : NSObject
@property (nonatomic, copy) NSString *srcObjectKey;
@property (nonatomic, copy) NSString *dstObjectKey;
@property (nonatomic, assign) BOOL success;
@property (nonatomic, strong) NSError *error;
@property (nonatomic, copy) NSString *etag;
@end
@implementation BatchCopyResult
@end
#pragma mark - Batch Copier
@interface BatchCopier : NSObject
@property (nonatomic, strong) OBSClient *client;
@property (nonatomic, copy) NSString *srcBucketName;
@property (nonatomic, copy) NSString *dstBucketName;
@property (nonatomic, assign) NSInteger concurrentCount;
@property (nonatomic, assign) OBSMetaDirective metadataDirective;
@property (nonatomic, strong) NSDictionary<NSString *, NSString *> *customMetadata;
@property (nonatomic, strong) NSMutableArray<BatchCopyResult *> *results;
@property (nonatomic, strong) dispatch_queue_t resultQueue;
- (instancetype)initWithClient:(OBSClient *)client
srcBucketName:(NSString *)srcBucketName
dstBucketName:(NSString *)dstBucketName
concurrentCount:(NSInteger)concurrentCount;
- (void)copyObjects:(NSArray<NSString *> *)srcObjectKeys
toObjectKeys:(NSArray<NSString *> *)dstObjectKeys
waitUntilDone:(BOOL)wait;
@end
@implementation BatchCopier
- (instancetype)initWithClient:(OBSClient *)client
srcBucketName:(NSString *)srcBucketName
dstBucketName:(NSString *)dstBucketName
concurrentCount:(NSInteger)concurrentCount {
self = [super init];
if (self) {
_client = client;
_srcBucketName = srcBucketName;
_dstBucketName = dstBucketName;
_concurrentCount = MIN(concurrentCount > 0 ? concurrentCount : 3, 10);
_metadataDirective = OBSMetaDirectiveCopy;
_results = [NSMutableArray array];
_resultQueue = dispatch_queue_create("com.obs.batchcopy.result", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)copyObjects:(NSArray<NSString *> *)srcObjectKeys
toObjectKeys:(NSArray<NSString *> *)dstObjectKeys
waitUntilDone:(BOOL)wait {
if (srcObjectKeys.count != dstObjectKeys.count) {
NSLog(@"[BatchCopy] ERROR: source and destination key arrays must have the same count");
return;
}
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.batchcopy", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"[BatchCopy] Starting %lu copy operations, concurrency: %ld, src: %@ -> dst: %@",
(unsigned long)srcObjectKeys.count, (long)self.concurrentCount, self.srcBucketName, self.dstBucketName);
for (NSUInteger i = 0; i < srcObjectKeys.count; i++) {
NSString *srcKey = srcObjectKeys[i];
NSString *dstKey = dstObjectKeys[i];
if (srcKey.length == 0 || dstKey.length == 0) {
continue;
}
dispatch_group_enter(group);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC));
[self copyObjectFrom:srcKey to:dstKey completion:^{
dispatch_semaphore_signal(semaphore);
dispatch_group_leave(group);
}];
});
}
if (wait) {
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC));
[self printSummary];
}
}
- (void)copyObjectFrom:(NSString *)srcObjectKey
to:(NSString *)dstObjectKey
completion:(void (^)(void))completion {
// 注意:copyObject不做源对象存在性预检,源不存在时服务端返回404
OBSCopyObjectRequest *request = [[OBSCopyObjectRequest alloc] initWithSrcBucketName:self.srcBucketName
srcObjectKey:srcObjectKey
dstBucketName:self.dstBucketName
dstObjectKey:dstObjectKey];
// 注意:如需替换元数据,需设metadataDirective=OBSMetaDirectiveReplace并设customMetadata
request.dstObjectMetaDirective = self.metadataDirective;
if (self.metadataDirective == OBSMetaDirectiveReplace && self.customMetadata.count > 0) {
request.metaDataDict = [self.customMetadata mutableCopy];
}
__weak typeof(self) weakSelf = self; __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return;
[self.client copyObject:request completionHandler:^(OBSCopyObjectResponse *response, NSError *error) {
BatchCopyResult *result = [[BatchCopyResult alloc] init];
result.srcObjectKey = srcObjectKey;
result.dstObjectKey = dstObjectKey;
result.error = error;
result.success = (error == nil);
result.etag = response.etag;
if (error) {
NSLog(@"[Copy] FAILED - %@ -> %@ : %@", srcObjectKey, dstObjectKey, error.localizedDescription);
} else {
NSLog(@"[Copy] SUCCESS - %@ (%@) -> %@ (%@)",
srcObjectKey, strongSelf.srcBucketName, dstObjectKey, strongSelf.dstBucketName);
}
dispatch_sync(strongSelf.resultQueue, ^{ [strongSelf.results addObject:result]; });
if (completion) completion();
}];
}
- (void)printSummary {
NSInteger success = 0, failure = 0;
for (BatchCopyResult *r in self.results) {
if (r.success) success++;
else failure++;
}
NSLog(@"\n========== Batch Copy Summary ==========");
NSLog(@"Total: %lu | Success: %ld | Failed: %ld", (unsigned long)self.results.count, (long)success, (long)failure);
if (failure > 0) {
NSLog(@"Failed items:");
for (BatchCopyResult *r in self.results) {
if (!r.success) {
NSLog(@" - %@ -> %@ : %@", r.srcObjectKey, r.dstObjectKey, 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];
// Source and destination buckets
// Set dstBucketName equal to srcBucketName for in-bucket copy
NSString *srcBucketName = @"your-source-bucket";
NSString *dstBucketName = @"your-destination-bucket";
NSInteger concurrentCount = 3; // Adjust as needed
// Source object keys
NSArray<NSString *> *srcObjectKeys = @[
@"source_folder/file1.txt",
@"source_folder/file2.jpg",
@"source_folder/file3.pdf",
@"source_folder/file4.mp4",
@"source_folder/file5.zip"
];
// Destination object keys (must match srcObjectKeys count)
NSString *dstPrefix = @"backup/";
NSMutableArray<NSString *> *dstObjectKeys = [NSMutableArray arrayWithCapacity:srcObjectKeys.count];
for (NSString *srcKey in srcObjectKeys) {
// 注意:lastPathComponent只取文件名,丢失中间目录结构
[dstObjectKeys addObject:[dstPrefix stringByAppendingString:[srcKey lastPathComponent]]];
}
// === Initialize OBS Client ===
OBSStaticCredentialProvider *credentialProvider = [[OBSStaticCredentialProvider alloc] initWithAccessKey:AK secretKey:SK];
OBSServiceConfiguration *conf = [[OBSServiceConfiguration alloc] initWithURLString:endPoint credentialProvider:credentialProvider];
OBSClient *client = [[OBSClient alloc] initWithConfiguration:conf];
// === Batch Copy ===
BatchCopier *copier = [[BatchCopier alloc] initWithClient:client
srcBucketName:srcBucketName
dstBucketName:dstBucketName
concurrentCount:concurrentCount];
// OBSMetaDirectiveCopy: keep source object's metadata (default)
// OBSMetaDirectiveReplace: use new metadata (set via customMetadata below)
copier.metadataDirective = OBSMetaDirectiveCopy;
// Optional: set custom metadata when using OBSMetaDirectiveReplace
// copier.customMetadata = @{@"X-Obs-Meta-Copied-By": @"BatchCopyTest", @"X-Obs-Meta-Copy-Time": [[NSDate date] description]};
[copier copyObjects:srcObjectKeys toObjectKeys:dstObjectKeys waitUntilDone:YES];
}
return 0;
} 父主题: 对象基础操作