Skip to content

Commit 9f009df

Browse files
authored
Progress closure for async/await APIs (#11289)
1 parent d74dae0 commit 9f009df

File tree

3 files changed

+109
-11
lines changed

3 files changed

+109
-11
lines changed

FirebaseStorage/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Unreleased
2+
- [added] Add progress tracking capability for `putDataAsync`, `putFileAsync`, and
3+
`writeAsync`. (#10574)
4+
15
# 10.10.0
26
- [fixed] Fixed potential memory leak of Storage instances. (#11248)
37

FirebaseStorage/Sources/AsyncAwait.swift

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,32 @@ import Foundation
4444
/// - uploadData: The Data to upload.
4545
/// - metadata: Optional StorageMetadata containing additional information (MIME type, etc.)
4646
/// about the object being uploaded.
47+
/// - onProgress: An optional closure function to return a `Progress` instance while the upload proceeds.
4748
/// - Throws:
4849
/// - An error if the operation failed, for example if Storage was unreachable.
4950
/// - Returns: StorageMetadata with additional information about the object being uploaded.
5051
func putDataAsync(_ uploadData: Data,
51-
metadata: StorageMetadata? = nil) async throws -> StorageMetadata {
52+
metadata: StorageMetadata? = nil,
53+
onProgress: ((Progress?) -> Void)? = nil) async throws -> StorageMetadata {
54+
guard let onProgress else {
55+
return try await withCheckedThrowingContinuation { continuation in
56+
self.putData(uploadData, metadata: metadata) { result in
57+
continuation.resume(with: result)
58+
}
59+
}
60+
}
61+
let uploadTask = putData(uploadData, metadata: metadata)
5262
return try await withCheckedThrowingContinuation { continuation in
53-
_ = self.putData(uploadData, metadata: metadata) { result in
54-
continuation.resume(with: result)
63+
uploadTask.observe(.progress) {
64+
onProgress($0.progress)
65+
}
66+
uploadTask.observe(.success) { _ in
67+
continuation.resume(with: .success(uploadTask.metadata!))
68+
}
69+
uploadTask.observe(.failure) { snapshot in
70+
continuation.resume(with: .failure(
71+
snapshot.error ?? StorageError.internalError("Internal Storage Error in putDataAsync")
72+
))
5573
}
5674
}
5775
}
@@ -63,14 +81,32 @@ import Foundation
6381
/// - url: A URL representing the system file path of the object to be uploaded.
6482
/// - metadata: Optional StorageMetadata containing additional information (MIME type, etc.)
6583
/// about the object being uploaded.
84+
/// - onProgress: An optional closure function to return a `Progress` instance while the upload proceeds.
6685
/// - Throws:
6786
/// - An error if the operation failed, for example if no file was present at the specified `url`.
6887
/// - Returns: `StorageMetadata` with additional information about the object being uploaded.
6988
func putFileAsync(from url: URL,
70-
metadata: StorageMetadata? = nil) async throws -> StorageMetadata {
89+
metadata: StorageMetadata? = nil,
90+
onProgress: ((Progress?) -> Void)? = nil) async throws -> StorageMetadata {
91+
guard let onProgress else {
92+
return try await withCheckedThrowingContinuation { continuation in
93+
self.putFile(from: url, metadata: metadata) { result in
94+
continuation.resume(with: result)
95+
}
96+
}
97+
}
98+
let uploadTask = putFile(from: url, metadata: metadata)
7199
return try await withCheckedThrowingContinuation { continuation in
72-
_ = self.putFile(from: url, metadata: metadata) { result in
73-
continuation.resume(with: result)
100+
uploadTask.observe(.progress) {
101+
onProgress($0.progress)
102+
}
103+
uploadTask.observe(.success) { _ in
104+
continuation.resume(with: .success(uploadTask.metadata!))
105+
}
106+
uploadTask.observe(.failure) { snapshot in
107+
continuation.resume(with: .failure(
108+
snapshot.error ?? StorageError.internalError("Internal Storage Error in putFileAsync")
109+
))
74110
}
75111
}
76112
}
@@ -79,14 +115,32 @@ import Foundation
79115
///
80116
/// - Parameters:
81117
/// - fileUrl: A URL representing the system file path of the object to be uploaded.
118+
/// - onProgress: An optional closure function to return a `Progress` instance while the download proceeds.
82119
/// - Throws:
83120
/// - An error if the operation failed, for example if Storage was unreachable
84121
/// or `fileURL` did not reference a valid path on disk.
85122
/// - Returns: A `URL` pointing to the file path of the downloaded file.
86-
func writeAsync(toFile fileURL: URL) async throws -> URL {
123+
func writeAsync(toFile fileURL: URL,
124+
onProgress: ((Progress?) -> Void)? = nil) async throws -> URL {
125+
guard let onProgress else {
126+
return try await withCheckedThrowingContinuation { continuation in
127+
_ = self.write(toFile: fileURL) { result in
128+
continuation.resume(with: result)
129+
}
130+
}
131+
}
132+
let downloadTask = write(toFile: fileURL)
87133
return try await withCheckedThrowingContinuation { continuation in
88-
_ = self.write(toFile: fileURL) { result in
89-
continuation.resume(with: result)
134+
downloadTask.observe(.progress) {
135+
onProgress($0.progress)
136+
}
137+
downloadTask.observe(.success) { _ in
138+
continuation.resume(with: .success(fileURL))
139+
}
140+
downloadTask.observe(.failure) { snapshot in
141+
continuation.resume(with: .failure(
142+
snapshot.error ?? StorageError.internalError("Internal Storage Error in writeAsync")
143+
))
90144
}
91145
}
92146
}

FirebaseStorage/Tests/Integration/StorageAsyncAwait.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,25 @@ import XCTest
178178
XCTAssertNotNil(result)
179179
}
180180

181+
func testSimplePutFileWithAsyncProgress() async throws {
182+
var checkedProgress = false
183+
let ref = storage.reference(withPath: "ios/public/testSimplePutFile")
184+
let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
185+
let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
186+
let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
187+
try data.write(to: fileURL, options: .atomicWrite)
188+
var uploadedBytes: Int64 = -1
189+
let successMetadata = try await ref.putFileAsync(from: fileURL) { progress in
190+
if let completed = progress?.completedUnitCount {
191+
checkedProgress = true
192+
XCTAssertGreaterThanOrEqual(completed, uploadedBytes)
193+
uploadedBytes = completed
194+
}
195+
}
196+
XCTAssertEqual(successMetadata.size, 17)
197+
XCTAssertTrue(checkedProgress)
198+
}
199+
181200
func testSimpleGetData() async throws {
182201
let ref = storage.reference(withPath: "ios/public/1mb2")
183202
let result = try await ref.data(maxSize: 1024 * 1024)
@@ -267,11 +286,10 @@ import XCTest
267286

268287
task.observe(StorageTaskStatus.progress) { snapshot in
269288
XCTAssertNil(snapshot.error, "Error should be nil")
270-
guard let progress = snapshot.progress else {
289+
guard snapshot.progress != nil else {
271290
XCTFail("Missing progress")
272291
return
273292
}
274-
print("\(progress.completedUnitCount) of \(progress.totalUnitCount)")
275293
}
276294
task.observe(StorageTaskStatus.failure) { snapshot in
277295
XCTAssertNil(snapshot.error, "Error should be nil")
@@ -280,6 +298,28 @@ import XCTest
280298
waitForExpectations()
281299
}
282300

301+
func testSimpleGetFileWithAsyncProgressCallbackAPI() async throws {
302+
var checkedProgress = false
303+
let ref = storage.reference().child("ios/public/1mb")
304+
let url = URL(fileURLWithPath: "\(NSTemporaryDirectory())/hello.txt")
305+
let fileURL = url
306+
var downloadedBytes: Int64 = 0
307+
var resumeAtBytes = 256 * 1024
308+
let successURL = try await ref.writeAsync(toFile: fileURL) { progress in
309+
if let completed = progress?.completedUnitCount {
310+
checkedProgress = true
311+
XCTAssertGreaterThanOrEqual(completed, downloadedBytes)
312+
downloadedBytes = completed
313+
if completed > resumeAtBytes {
314+
resumeAtBytes = Int.max
315+
}
316+
}
317+
}
318+
XCTAssertTrue(checkedProgress)
319+
XCTAssertEqual(successURL, url)
320+
XCTAssertEqual(resumeAtBytes, Int.max)
321+
}
322+
283323
private func assertMetadata(actualMetadata: StorageMetadata,
284324
expectedContentType: String,
285325
expectedCustomMetadata: [String: String]) {

0 commit comments

Comments
 (0)