All Products
Search
Document Center

Object Storage Service:Resumable download (OSS SDK for iOS)

Last Updated:Jun 21, 2026

Resumable download lets you continue an interrupted download from where it left off. This feature saves time and network traffic.

Process

For example, if you are downloading a video on your phone and the network switches from Wi-Fi to a mobile network, the app interrupts the download by default. If you enable resumable download, the download resumes from where it left off when you switch back to Wi-Fi.

During the download, the app displays a progress dialog that shows the current download percentage, a progress bar, and buttons to pause or cancel the download.

image

Features

  • HTTP 1.1 supports the Range header. This header specifies the range of data to retrieve. The Range header supports the following formats.

    Timestamp range

    Description

    Range: bytes=100-

    Transfers data from byte 101 to the last byte.

    Range: bytes=100-200

    Transfers data from byte 101 to byte 201. This is often used for chunked transfer of large files, such as videos.

    Range: bytes=-100

    Transfers the last 100 bytes of the content, not the first 100 bytes.

    Range: bytes=0-100, 200-300

    Specifies multiple content ranges at the same time.

  • For a resumable download, you can use the If-Match header to check whether the file on the server has changed. The value of If-Match corresponds to the file's ETag value.

  • When a client sends a request, include the Range and If-Match headers. The OSS server receives the request and validates the ETag value in the If-Match header. If the values do not match, the server returns a 412 Precondition Failed status code.

  • The OSS server supports Range, If-Match, If-None-Match, If-Modified-Since, and If-Unmodified-Since for GetObject operations. This lets you implement resumable downloads for OSS resources on mobile devices.

Sample code

Important

The OSS SDK for iOS does not natively support resumable downloads. The following code is provided for reference only to help you understand the download process. Do not use this code in production projects. To implement resumable downloads, you must write your own code or use a third-party open source download framework.

The following sample code demonstrates how to perform a resumable download in iOS:

#import "DownloadService.h"
#import "OSSTestMacros.h"
@implementation DownloadRequest
@end
@implementation Checkpoint
- (instancetype)copyWithZone:(NSZone *)zone {
    Checkpoint *other = [[[self class] allocWithZone:zone] init];
    other.etag = self.etag;
    other.totalExpectedLength = self.totalExpectedLength;
    return other;
}
@end
@interface DownloadService()<NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (nonatomic, strong) NSURLSession *session;         // Network session.
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;   // Data request task.
@property (nonatomic, copy) DownloadFailureBlock failure;    // Called on request failure.
@property (nonatomic, copy) DownloadSuccessBlock success;    // Called on request success.
@property (nonatomic, copy) DownloadProgressBlock progress;  // Tracks download progress.
@property (nonatomic, copy) Checkpoint *checkpoint;        // The checkpoint.
@property (nonatomic, copy) NSString *requestURLString;    // The URL of the resource to download.
@property (nonatomic, copy) NSString *headURLString;       // The URL for the HEAD request.
@property (nonatomic, copy) NSString *targetPath;     // The local path to store the file.
@property (nonatomic, assign) unsigned long long totalReceivedContentLength; // The size of the content received so far.
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation DownloadService
- (instancetype)init
{
    self = [super init];
    if (self) {
        NSURLSessionConfiguration *conf = [NSURLSessionConfiguration defaultSessionConfiguration];
        conf.timeoutIntervalForRequest = 15;
        NSOperationQueue *processQueue = [NSOperationQueue new];
        _session = [NSURLSession sessionWithConfiguration:conf delegate:self delegateQueue:processQueue];
        _semaphore = dispatch_semaphore_create(0);
        _checkpoint = [[Checkpoint alloc] init];
    }
    return self;
}
// DownloadRequest is the core of the download logic.
+ (instancetype)downloadServiceWithRequest:(DownloadRequest *)request {
    DownloadService *service = [[DownloadService alloc] init];
    if (service) {
        service.failure = request.failure;
        service.success = request.success;
        service.requestURLString = request.sourceURLString;
        service.headURLString = request.headURLString;
        service.targetPath = request.downloadFilePath;
        service.progress = request.downloadProgress;
        if (request.checkpoint) {
            service.checkpoint = request.checkpoint;
        }
    }
    return service;
}
/**
 * Gets file information by using the HEAD method and compares the file's ETag with the one in the local checkpoint.
 */
- (BOOL)getFileInfo {
    __block BOOL resumable = NO;
    NSURL *url = [NSURL URLWithString:self.headURLString];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url];
    [request setHTTPMethod:@"HEAD"];
    // Process object information. For example, the ETag is used for pre-checks in resumable downloads, and content-length is used to calculate download progress.
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"Failed to get file metadata. Error: %@", error);
        } else {
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
            NSString *etag = [httpResponse.allHeaderFields objectForKey:@"Etag"];
            if ([self.checkpoint.etag isEqualToString:etag]) {
                resumable = YES;
            } else {
                resumable = NO;
            }
        }
        dispatch_semaphore_signal(self.semaphore);
    }];
    [task resume];
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    return resumable;
}
/**
 * Gets the size of the local file.
 */
- (unsigned long long)fileSizeAtPath:(NSString *)filePath {
    unsigned long long fileSize = 0;
    NSFileManager *dfm = [NSFileManager defaultManager];
    if ([dfm fileExistsAtPath:filePath]) {
        NSError *error = nil;
        NSDictionary *attributes = [dfm attributesOfItemAtPath:filePath error:&error];
        if (!error && attributes) {
            fileSize = attributes.fileSize;
        } else if (error) {
            NSLog(@"error: %@", error);
        }
    }
    return fileSize;
}
- (void)resume {
    NSURL *url = [NSURL URLWithString:self.requestURLString];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url];
    [request setHTTPMethod:@"GET"];
    BOOL resumable = [self getFileInfo];    // A `NO` return value indicates that the conditions for a resumable download are not met.
    if (resumable) {
        self.totalReceivedContentLength = [self fileSizeAtPath:self.targetPath];
        NSString *requestRange = [NSString stringWithFormat:@"bytes=%llu-", self.totalReceivedContentLength];
        [request setValue:requestRange forHTTPHeaderField:@"Range"];
    } else {
        self.totalReceivedContentLength = 0;
    }
    if (self.totalReceivedContentLength == 0) {
        [[NSFileManager defaultManager] createFileAtPath:self.targetPath contents:nil attributes:nil];
    }
    self.dataTask = [self.session dataTaskWithRequest:request];
    [self.dataTask resume];
}
- (void)pause {
    [self.dataTask cancel];
    self.dataTask = nil;
}
- (void)cancel {
    [self.dataTask cancel];
    self.dataTask = nil;
    [self removeFileAtPath: self.targetPath];
}
- (void)removeFileAtPath:(NSString *)filePath {
    NSError *error = nil;
    [[NSFileManager defaultManager] removeItemAtPath:self.targetPath error:&error];
    if (error) {
        NSLog(@"remove file with error : %@", error);
    }
}
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
// Determines if the download is complete and reports the result to the upper-layer service.
didCompleteWithError:(nullable NSError *)error {
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
    if ([httpResponse isKindOfClass:[NSHTTPURLResponse class]]) {
        if (httpResponse.statusCode == 200) {
            self.checkpoint.etag = [[httpResponse allHeaderFields] objectForKey:@"Etag"];
            self.checkpoint.totalExpectedLength = httpResponse.expectedContentLength;
        } else if (httpResponse.statusCode == 206) {
            self.checkpoint.etag = [[httpResponse allHeaderFields] objectForKey:@"Etag"];
            self.checkpoint.totalExpectedLength = self.totalReceivedContentLength + httpResponse.expectedContentLength;
        }
    }
    if (error) {
        if (self.failure) {
            NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
            [userInfo oss_setObject:self.checkpoint forKey:@"checkpoint"];
            NSError *tError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
            self.failure(tError);
        }
    } else if (self.success) {
        self.success(@{@"status": @"success"});
    }
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)dataTask.response;
    if ([httpResponse isKindOfClass:[NSHTTPURLResponse class]]) {
        if (httpResponse.statusCode == 200) {
            self.checkpoint.totalExpectedLength = httpResponse.expectedContentLength;
        } else if (httpResponse.statusCode == 206) {
            self.checkpoint.totalExpectedLength = self.totalReceivedContentLength +  httpResponse.expectedContentLength;
        }
    }
    completionHandler(NSURLSessionResponseAllow);
}
// Appends the received data to the file and updates the download progress.
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.targetPath];
    [fileHandle seekToEndOfFile];
    [fileHandle writeData:data];
    [fileHandle closeFile];
    self.totalReceivedContentLength += data.length;
    if (self.progress) {
        self.progress(data.length, self.totalReceivedContentLength, self.checkpoint.totalExpectedLength);
    }
}
@end
  • DownloadRequest definition

    #import <Foundation/Foundation.h>
    typedef void(^DownloadProgressBlock)(int64_t bytesReceived, int64_t totalBytesReceived, int64_t totalBytesExpectToReceived);
    typedef void(^DownloadFailureBlock)(NSError *error);
    typedef void(^DownloadSuccessBlock)(NSDictionary *result);
    @interface Checkpoint : NSObject<NSCopying>
    @property (nonatomic, copy) NSString *etag;     // The ETag of the resource.
    @property (nonatomic, assign) unsigned long long totalExpectedLength;    // The total size of the file.
    @end
    @interface DownloadRequest : NSObject
    @property (nonatomic, copy) NSString *sourceURLString;      // The download URL.
    @property (nonatomic, copy) NSString *headURLString;        // The URL to get file metadata.
    @property (nonatomic, copy) NSString *downloadFilePath;     // The local storage path for the file.
    @property (nonatomic, copy) DownloadProgressBlock downloadProgress; // The callback for download progress.
    @property (nonatomic, copy) DownloadFailureBlock failure;   // The callback for download failure.
    @property (nonatomic, copy) DownloadSuccessBlock success;   // The callback for download success.
    @property (nonatomic, copy) Checkpoint *checkpoint;         // Stores the ETag of the file.
    @end
    @interface DownloadService : NSObject
    + (instancetype)downloadServiceWithRequest:(DownloadRequest *)request;
    /**
     * Starts the download.
     */
    - (void)resume;
    /**
     * Pauses the download.
     */
    - (void)pause;
    /**
     * Cancels the download.
     */
    - (void)cancel;
    @end
  • Upper-layer service call

    - (void)initDownloadURLs {
        OSSPlainTextAKSKPairCredentialProvider *pCredential = [[OSSPlainTextAKSKPairCredentialProvider alloc] initWithPlainTextAccessKey:OSS_ACCESSKEY_ID secretKey:OSS_SECRETKEY_ID];
        _mClient = [[OSSClient alloc] initWithEndpoint:OSS_ENDPOINT credentialProvider:pCredential];
        // Generate a signed URL for GET requests.
        OSSTask *downloadURLTask = [_mClient presignConstrainURLWithBucketName:@"aliyun-dhc-shanghai" withObjectKey:OSS_DOWNLOAD_FILE_NAME withExpirationInterval:1800];
        [downloadURLTask waitUntilFinished];
        _downloadURLString = downloadURLTask.result;
        // Generate a signed URL for HEAD requests.
        OSSTask *headURLTask = [_mClient presignConstrainURLWithBucketName:@"aliyun-dhc-shanghai" withObjectKey:OSS_DOWNLOAD_FILE_NAME httpMethod:@"HEAD" withExpirationInterval:1800 withParameters:nil];
        [headURLTask waitUntilFinished];
        _headURLString = headURLTask.result;
    }
    - (IBAction)resumeDownloadClicked:(id)sender {
        _downloadRequest = [DownloadRequest new];
        _downloadRequest.sourceURLString = _downloadURLString;       // Set the URL of the resource.
        _downloadRequest.headURLString = _headURLString;
        NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
        _downloadRequest.downloadFilePath = [documentPath stringByAppendingPathComponent:OSS_DOWNLOAD_FILE_NAME];   // Set the local path to save the downloaded file.
        __weak typeof(self) wSelf = self;
        _downloadRequest.downloadProgress = ^(int64_t bytesReceived, int64_t totalBytesReceived, int64_t totalBytesExpectToReceived) {
            // totalBytesReceived is the number of bytes already downloaded. totalBytesExpectToReceived is the total expected size of the file.
            dispatch_async(dispatch_get_main_queue(), ^{
                __strong typeof(self) sSelf = wSelf;
                CGFloat fProgress = totalBytesReceived * 1.f / totalBytesExpectToReceived;
                sSelf.progressLab.text = [NSString stringWithFormat:@"%.2f%%", fProgress * 100];
                sSelf.progressBar.progress = fProgress;
            });
        };
        _downloadRequest.failure = ^(NSError *error) {
            __strong typeof(self) sSelf = wSelf;
            sSelf.checkpoint = error.userInfo[@"checkpoint"];
        };
        _downloadRequest.success = ^(NSDictionary *result) {
            NSLog(@"Download successful");
        };
        _downloadRequest.checkpoint = self.checkpoint;
        NSString *titleText = [[_downloadButton titleLabel] text];
        if ([titleText isEqualToString:@"download"]) {
            [_downloadButton setTitle:@"pause" forState: UIControlStateNormal];
            _downloadService = [DownloadService downloadServiceWithRequest:_downloadRequest];
            [_downloadService resume];
        } else {
            [_downloadButton setTitle:@"download" forState: UIControlStateNormal];
            [_downloadService pause];
        }
    }
    - (IBAction)cancelDownloadClicked:(id)sender {
        [_downloadButton setTitle:@"download" forState: UIControlStateNormal];
        [_downloadService cancel];
    }
Note

You can obtain the checkpoint from the failure callback when you pause or cancel a download. When you restart the download, you can pass the checkpoint to DownloadRequest. DownloadService then uses the checkpoint to perform a consistency check.