An iOS client written in Swift
for TUS resumable upload protocol.
With this client, you can upload regular raw Data
or file-paths.
You can refer to the example project to see how TUSKit is implemented.
As a starting point, please refer to the SceneDelegate.
Here is how you can instantiate a TUSClient
instance.
Be sure to store a reference to the client somewhere. Then initialize as you normally would.
final class MyClass {
let tusClient: TUSClient
init() {
tusClient = TUSClient(server: URL(string: "https://tusd.tusdemo.net/files")!, sessionIdentifier: "TUS DEMO", storageDirectory: URL(string: "TUS")!)
tusClient.delegate = self
}
}
Note that you can register as a delegate to retrieve the URL's of the uploads, and also any errors that may arise.
Note that you can pass your own URLSession
instance to the initializer.
You can conform to the TUSClientDelegate
to receive updates from the TUSClient
.
extension MyClass: TUSClientDelegate {
func didStartUpload(id: UUID, client: TUSClient) {
print("TUSClient started upload, id is \(id)")
print("TUSClient remaining is \(client.remainingUploads)")
}
func didFinishUpload(id: UUID, url: URL, client: TUSClient) {
print("TUSClient finished upload, id is \(id) url is \(url)")
print("TUSClient remaining is \(client.remainingUploads)")
if client.remainingUploads == 0 {
print("Finished uploading")
}
}
func uploadFailed(id: UUID, error: Error, client: TUSClient) {
print("TUSClient upload failed for \(id) error \(error)")
}
func fileError(error: TUSClientError, client: TUSClient) {
print("TUSClient File error \(error)")
}
func totalProgress(bytesUploaded: Int, totalBytes: Int, client: TUSClient) {
}
func progressFor(id: UUID, bytesUploaded: Int, totalBytes: Int, client: TUSClient) {
}
}
A TUSClient
can upload Data
or paths to a file in the form of a URL
.
To upload data, use the upload(data:)
method`
let data = Data("I am some data".utf8)
let uploadId = try tusClient.upload(data: data)
To upload multiple data files at once, use the uploadMultiple(dataFiles:)
method.
To upload a single stored file, retrieve a file path and pass it to the client.
let pathToFile:URL = ...
let uploadId = try tusClient.uploadFileAt(filePath: pathToFile)
To upload multiple files at once, you can use the uploadFiles(filePaths:)
method.
To specify a custom upload URL (e.g. for TransloadIt) or custom headers to be added to a file upload, please refer to the uploadURL
and customHeaders
properties in the methods related to uploading. Such as: upload
, uploadFileAt
, uploadFiles
or uploadMultiple(dataFiles:)
.
To know how many files have yet to be uploaded, please refer to the remainingUploads
property.
Please note that there isn't a percentage supplied, since it's up to you to define what the starting point is of an upload. For example. If you upload 10 files, and 3 are finished, then you are at 3/10. However, if during this upload you add 2 more, should that count as 3/12 or do you consider it a a fresh upload? So 0/9. It's up to you to define how finished uploads are counted when adding new uploads.
For byte level progress. Please implement the TUSClientDelegate
protocol and set it as a the delegate
property of TUSClient
.
By starting an upload you will receive an id. These id's are also passed to you via if you implement the TUSClientDelegate
.
You can use these id's to identify which files are finished or failed (but you can also use contexts for that, see below). You can also delete these files on failure if you want. You can also use these id's to retry a failed upload.
Note that TUSClient
will automatically retry an upload a few times, but will eventually give up, after which it will report an error. After which you can call the retry
method and try again.
You can use id's to monitor progress and perform other tasks, such as stopping uploads. But you can also pass a context with richer information. TUSKit will return this context through various delegate calls. This way you don't have to keep track of the status of upload id's. You can pass in a small object with information, and you receive this from TUSKit.
Security notice: TUSKit will store this context on the disk next to other file metadata. This is to maintain the information between sessions.
An upload can fail at any time. Even when an app is in the background.
Therefore, after starting a new app session, we recommend you inspect any failed uploads that may have occurred and act accordingly. For instance, you can decide to do something with the failed uploads such as retrying them, deleting them, or reporting to the user.
For instance, here is how you can initialize the client and check its failed uploads. Note that we first fetch the id's, after which retry the uploads.
tusClient = TUSClient(server: URL(string: "https://tusd.tusdemo.net/files")!, sessionIdentifier: "TUS DEMO", storageDirectory: URL(string: "/TUS")!)
tusClient.delegate = self
tusClient.start()
do {
// When starting, you can retrieve the locally stored uploads that are marked as failure, and handle those.
// E.g. Maybe some uploads failed from a last session, or failed from a background upload.
let ids = try tusClient.failedUploadIds()
for id in ids {
// You can either retry a failed upload...
try tusClient.retry(id: id)
// ...alternatively, you can delete them too
// tusClient.removeCacheFor(id: id)
}
} catch {
// Could not fetch failed id's from disk
}
When you incorporate background uploading, we strongly recommend you to inspect any failed uploads that may have occured in the background. Please refer to Starting a new Session for more information.
iOS can leverage a background URLSession to enable background uploads that continue when a user leaves your app or locks their device. For more information, take a look at Apple's docs on background URLSession. The docs focus on downloads but uploads follow pretty much the exact same principles.
To make incorporating background uploads as straightforward as possible, TUSKit handles all the complex details of managing the URLSession and delegate callbacks. As a consumer of TUSKit all you need to do is leverage the new initializer on TUSClient
as shown below:
do {
tusClient = try TUSClient(
server: URL(string: "https://tusd.tusdemo.net/files")!,
sessionIdentifier: "TUS DEMO",
sessionConfiguration: .background(withIdentifier: "com.TUSKit.sample"),
storageDirectory: URL(string: "/TUS")!,
chunkSize: 0
)
} catch {
// ...
}
The easiest way to set everything up is to pass a URLSession.background
configuration to the TUSClient
. If you require a different configuration or don't want any background support at all, you're free to pass a different configuration.
Because TUSKit can now have uploads running while your app is no longer actively in memory, you should always use the getStoredUpload
method on TUSClient
on app launch to retrieve all stored uploads and extract information about which uploads are currently completed. Afterwards you can call cleanup
to allow TUSClient
to remove metadata for completed items. See the sample app for more details.
The primary source for information on how background uploads work will always be Apple's own documentation on performing network calls in the background. TUSKit is limited and bound by what's possible and allowed by the system.
In practice, this means that you should always schedule all of your uploads while the app is in the foreground. Scheduling more uploads while an app is in the background is not allowed and will result in the system deprioritizing your requests.
For this reason, chunking is strongly discouraged when you intend to leverage background uploads since you would have to make several network calls, one after the other, to complete your upload.
It's also important to understand that iOS will automatically wait for your user to have a network connection before attempting to start your upload. This means your uploads might take a while to start. If a network connection drops while an upload is in progress, iOS will attempt to re-upload your file automatically.
By default, iOS will retry the full upload instead of resuming where the upload has left off. Unfortunately we haven't found a way to prevent iOS from doing this. On iOS 17 Apple has adde support for resumable uploads. We're currently exploring how we can integrate TUSKit with this feature since both iOS and TUSKit rely on the same protocols.
Available from iOS13, you can schedule uploads to be performed in the background using the scheduleBackgroundTasks()
method on TUSClient
.
Scheduled tasks are handled by iOS. Which means that each device will decide when it's best to upload in the background. Such as when it has a wifi connection and late at night.
As an example from the SceneDelegate
found in the example app, you can schedule them accordingly:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// ... snip
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
tusClient.scheduleBackgroundTasks()
}
}
The client assumes by default that the server implements the Creation TUS protocol extension. If your server does not support that, please ensure to provide an empty array for the supportedExtensions
parameter in the client initializer.
Please refer to the example app inside this project to see how to add photos from a PHPicker, using SwiftUI. You can also use the PHPicker
mechanic for UIKit.
At the time of writing, this client does not support TUS' concatenation option. It does, however, automatically support parallel uploads in a single client. It does also support multiple clients.
The TUSClient
will retry a failed upload two times (three total attempts) before reporting it as an error.
The TUSClient
will try to upload a file fully, and if it gets interrupted (e.g. broken connection or app is killed), it will continue where it left of.
The TUSClient
stores files locally to upload them. It will use the storageDirectory
path that is passed in the initializer. Or create a default directory inside the documentsdir at /TUS .
The TUSClient
will automatically removed locally stored files once their upload is complete.
TUSClient
supports multiple instances for simultaneous unrelated uploads.
Warning: Multiple clients should not share the same storageDirectory
. Give each client their own directory to work from, or bugs may occur.
Please note that TUSClient
since version 3.0.0 is not a singleton anymore.
If you strongly feel you want a singleton, you can still make one using the static keyword.
final class MyClass {
static let tusClient: TUSClient = ...
}
But we discourage you from doing so because it makes resetting between tests harder, and it becomes problematic in a multi-threaded environment.