Multipart upload (Swift SDK)

Updated at:

Multipart upload splits a large object into parts, uploads them independently, then combines them into a single object using the CompleteMultipartUpload API operation.

Prerequisites

Before you begin, ensure that you have:

  • An OSS bucket in the target region

  • The oss:PutObject permission. For details, see Attach a custom policy to a RAM user

  • The OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables set with valid credentials

Usage notes

  • The sample code uses the China (Hangzhou) region (cn-hangzhou) with a public endpoint. If you access OSS from another Alibaba Cloud service in the same region, use an internal endpoint instead. For region and endpoint details, see Regions and endpoints.

How it works

A multipart upload consists of three steps:

  1. Initiate — Call initiateMultipartUpload. OSS returns a globally unique upload ID.

  2. Upload parts — Call uploadPart for each part, using the upload ID and a part number that identifies the part's position in the final object. OSS returns an ETag for each uploaded part.

  3. Complete — Call completeMultipartUpload with all part ETags. OSS assembles the parts into a single object.

Note: Uploading a new part with an existing part number overwrites the previously uploaded part. OSS includes the MD5 hash of the received part data in the ETag header of the response. OSS validates each part's MD5 hash and returns InvalidDigest if the hash does not match.

Upload a large file

The following example reads a local file, splits it into parts of 5 MB (5 * 1024 * 1024 bytes), and uploads each part sequentially. The last part may be smaller than the defined part size.

import AlibabaCloudOSS
import Foundation

@main
struct Main {
    static func main() async {
        do {
            // Specify the region where the bucket is located. For example, for the China (Hangzhou) region, set Region to cn-hangzhou.
            let region = "cn-hangzhou"
            // Specify the bucket name.
            let bucket = "yourBucketName"
            // Specify the object name, for example, my-object.txt.
            let key = "yourKey"
            // Specify the path of the local file, for example, /path/to/file.txt.
            let filePath = "/path/to/your/file.txt"
            // Set the part size in bytes. For example, 5 MB = 5 * 1024 * 1024.
            let partSize = 5 * 1024 * 1024
            // Optional. Specify the domain name used to access OSS. For example, for the China (Hangzhou) region, set Endpoint to https://oss-cn-hangzhou.aliyuncs.com.
            let endpoint: String? = nil

            // Load credentials from environment variables. You must set the OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables in advance.
            let credentialsProvider = EnvironmentCredentialsProvider()

            // Configure OSS client parameters.
            let config = Configuration.default()
                .withRegion(region) // Set the region.
                .withCredentialsProvider(credentialsProvider) // Set the credentials.

            // Set the endpoint.
            if let endpoint = endpoint {
                config.withEndpoint(endpoint)
            }

            // Create an OSS client instance.
            let client = Client(config)

            // 1. Initiate a multipart upload.
            let initResult = try await client.initiateMultipartUpload(
                InitiateMultipartUploadRequest(
                    bucket: bucket,
                    key: key
                )
            )
            let uploadId = initResult.uploadId // Obtain the upload ID.

            // 2. Obtain file attributes and calculate the number of parts.
            let attribute = try FileManager.default.attributesOfItem(atPath: filePath)
            guard let fileSize = attribute[FileAttributeKey.size] as? Int64 else {
                throw ClientError(code: "error", message: "Can't get file size")
            }

            var partCount = Int(fileSize / Int64(partSize))
            if fileSize % Int64(partSize) > 0 { partCount += 1 } // Handle cases where the last part is smaller than the defined part size.

            // 3. Open the file and upload it part by part.
            let fileHandle = FileHandle(forReadingAtPath: filePath)
            var parts: [UploadPart] = [] // Store the ETag and number for each part.

            for partNumber in 1...partCount {
                // Go to the starting position of the current part.
                fileHandle?.seek(toFileOffset: UInt64((partNumber - 1) * partSize))

                // Read the data of the current part.
                guard let partData = fileHandle?.readData(ofLength: partSize) else {
                    throw ClientError(code: "error", message: "Can't get file data")
                }

                // Upload the part.
                let uploadPartResult = try await client.uploadPart(
                    UploadPartRequest(
                        bucket: bucket,
                        key: key,
                        partNumber: partNumber,
                        uploadId: uploadId,
                        body: .data(partData)
                    )
                )

                // Save the ETag and number of the part.
                parts.append(
                    UploadPart(
                        etag: uploadPartResult.etag,
                        partNumber: partNumber
                    )
                )
            }

            // 4. Complete the multipart upload.
            let _ = try await client.completeMultipartUpload(
                CompleteMultipartUploadRequest(
                    bucket: bucket,
                    key: key,
                    uploadId: uploadId,
                    completeMultipartUpload: CompleteMultipartUpload(parts: parts)
                )
            )
            print("Multipart upload completed!")

        } catch {
            // Catch and handle exceptions.
            print("error:\n\(error)")
        }
    }
}

More operations

Cancel a multipart upload

After you cancel a multipart upload, the upload ID becomes invalid and OSS deletes all parts uploaded under that ID.

import AlibabaCloudOSS
import Foundation

@main
struct Main {
    static func main() async {
        do {
            // Specify the region where the bucket is located. For example, for the China (Hangzhou) region, set Region to cn-hangzhou.
            let region = "cn-hangzhou"
            // Specify the bucket name.
            let bucket = "yourBucketName"
            // Optional. Specify the domain name used to access OSS. For example, for the China (Hangzhou) region, set Endpoint to https://oss-cn-hangzhou.aliyuncs.com.
            let endpoint: String? = nil
            // Specify the object name, for example, my-object.txt.
            let key = "yourKey"
            // Specify the upload ID. Obtain this ID from the response of the InitiateMultipartUpload operation.
            let uploadId = "yourUploadId"

            // Load credentials from environment variables. You must set the OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables in advance.
            let credentialsProvider = EnvironmentCredentialsProvider()

            // Configure OSS client parameters.
            let config = Configuration.default()
                .withRegion(region) // Set the region.
                .withCredentialsProvider(credentialsProvider) // Set the credentials.

            // Set the endpoint.
            if let endpoint = endpoint {
                config.withEndpoint(endpoint)
            }

            // Create an OSS client instance.
            let client = Client(config)

            // Abort the multipart upload.
            let result = try await client.abortMultipartUpload(
                AbortMultipartUploadRequest(
                    bucket: bucket,
                    key: key,
                    uploadId: uploadId
                )
            )

            // Print the result.
            print("result:\n\(result)")

        } catch {
            // Catch and handle exceptions.
            print("error:\n\(error)")
        }
    }
}

List uploaded parts

Call listPartsPaginator with a valid upload ID to list all parts successfully uploaded under that ID. Do this before calling completeMultipartUpload to verify all parts are present.

import AlibabaCloudOSS
import Foundation

@main
struct Main {
    static func main() async {
        do {
            // Specify the region where the bucket is located. For example, for the China (Hangzhou) region, set Region to cn-hangzhou.
            let region = "cn-hangzhou"
            // Specify the bucket name.
            let bucket = "yourBucketName"
            // Optional. Specify the domain name used to access OSS. For example, for the China (Hangzhou) region, set Endpoint to https://oss-cn-hangzhou.aliyuncs.com.
            let endpoint: String? = nil
            // Specify the object name, for example, my-object.txt.
            let key = "yourKey"
            // Specify the upload ID. Obtain this ID from the response of the InitiateMultipartUpload operation.
            let uploadId = "yourUploadId"

            // Load credentials from environment variables. You must set the OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables in advance.
            let credentialsProvider = EnvironmentCredentialsProvider()

            // Configure OSS client parameters.
            let config = Configuration.default()
                .withRegion(region) // Set the region.
                .withCredentialsProvider(credentialsProvider) // Set the credentials.

            // Set the endpoint.
            if let endpoint = endpoint {
                config.withEndpoint(endpoint)
            }

            // Create an OSS client instance.
            let client = Client(config)

            // Create a paginator for the part list to traverse part information by page.
            let paginator = client.listPartsPaginator(
                ListPartsRequest(
                    bucket: bucket,
                    key: key,
                    uploadId: uploadId
                )
            )

            // Traverse all parts and print their information.
            for try await page in paginator {
                for part in page.parts ?? [] {
                    print("Part number: \(String(describing: part.partNumber)), ETag: \(part.etag ?? ""), Size: \(String(describing: part.size)) , Last modified: \(String(describing: part.lastModified))")
                }
            }

        } catch {
            // Catch and handle exceptions.
            print("error:\n\(error)")
        }
    }
}

List in-progress multipart uploads

Call listMultipartUploadsPaginator to list all incomplete multipart upload tasks for a bucket. This is useful for identifying stale uploads to clean up.

import AlibabaCloudOSS
import Foundation

@main
struct Main {
    static func main() async {
        do {
            // Specify the region where the bucket is located. For example, for the China (Hangzhou) region, set Region to cn-hangzhou.
            let region = "cn-hangzhou"
            // Specify the bucket name.
            let bucket = "yourBucketName"
            // Optional. Specify the domain name used to access OSS. For example, for the China (Hangzhou) region, set Endpoint to https://oss-cn-hangzhou.aliyuncs.com.
            let endpoint: String? = nil

            // Load credentials from environment variables. You must set the OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables in advance.
            let credentialsProvider = EnvironmentCredentialsProvider()

            // Configure OSS client parameters.
            let config = Configuration.default()
                .withRegion(region) // Set the region.
                .withCredentialsProvider(credentialsProvider) // Set the credentials.

            // Set the endpoint.
            if let endpoint = endpoint {
                config.withEndpoint(endpoint)
            }

            // Create an OSS client instance.
            let client = Client(config)

            // Create a paginator to traverse incomplete multipart upload tasks by page.
            let paginator = client.listMultipartUploadsPaginator(
                ListMultipartUploadsRequest(
                    bucket: bucket
                )
            )

            // Traverse all incomplete multipart upload tasks.
            for try await page in paginator {
                for upload in page.uploads ?? [] {
                    print("Object name: \(upload.key ?? ""), Upload ID: \(upload.uploadId ?? ""), Initiated time: \(upload.initiated!)")
                }
            }

        } catch {
            // Catch and handle exceptions.
            print("error:\n\(error)")
        }
    }
}

What's next