Skip to content

Commit 597de2b

Browse files
committed
[Core] Make storage class conform to Sendable
1 parent 8328630 commit 597de2b

File tree

4 files changed

+37
-47
lines changed

4 files changed

+37
-47
lines changed

FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,20 @@ import Foundation
1717
/// A type that can perform atomic operations using block-based transformations.
1818
protocol HeartbeatStorageProtocol {
1919
func readAndWriteSync(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?)
20-
func readAndWriteAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?)
20+
func readAndWriteAsync(using transform: @escaping @Sendable (HeartbeatsBundle?)
21+
-> HeartbeatsBundle?)
2122
func getAndSet(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) throws
2223
-> HeartbeatsBundle?
23-
func getAndSetAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?,
24-
completion: @escaping (Result<HeartbeatsBundle?, Error>) -> Void)
24+
func getAndSetAsync(using transform: @escaping @Sendable (HeartbeatsBundle?) -> HeartbeatsBundle?,
25+
completion: @escaping @Sendable (Result<HeartbeatsBundle?, Error>) -> Void)
2526
}
2627

2728
/// Thread-safe storage object designed for transforming heartbeat data that is persisted to disk.
28-
final class HeartbeatStorage: HeartbeatStorageProtocol {
29+
final class HeartbeatStorage: Sendable, HeartbeatStorageProtocol {
2930
/// The identifier used to differentiate instances.
3031
private let id: String
3132
/// The underlying storage container to read from and write to.
32-
private let storage: Storage
33+
private let storage: any Storage
3334
/// The encoder used for encoding heartbeat data.
3435
private let encoder: JSONEncoder = .init()
3536
/// The decoder used for decoding heartbeat data.
@@ -107,7 +108,8 @@ final class HeartbeatStorage: HeartbeatStorageProtocol {
107108
/// Asynchronously reads from and writes to storage using the given transform block.
108109
/// - Parameter transform: A block to transform the currently stored heartbeats bundle to a new
109110
/// heartbeats bundle value.
110-
func readAndWriteAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?) {
111+
func readAndWriteAsync(using transform: @escaping @Sendable (HeartbeatsBundle?)
112+
-> HeartbeatsBundle?) {
111113
queue.async { [self] in
112114
let oldHeartbeatsBundle = try? load(from: storage)
113115
let newHeartbeatsBundle = transform(oldHeartbeatsBundle)
@@ -143,8 +145,8 @@ final class HeartbeatStorage: HeartbeatStorageProtocol {
143145
/// - completion: An escaping block used to process the heartbeat data that
144146
/// was stored (before the `transform` was applied); otherwise, the error
145147
/// that occurred.
146-
func getAndSetAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?,
147-
completion: @escaping (Result<HeartbeatsBundle?, Error>) -> Void) {
148+
func getAndSetAsync(using transform: @escaping @Sendable (HeartbeatsBundle?) -> HeartbeatsBundle?,
149+
completion: @escaping @Sendable (Result<HeartbeatsBundle?, Error>) -> Void) {
148150
queue.async {
149151
do {
150152
let oldHeartbeatsBundle = try? self.load(from: self.storage)

FirebaseCore/Internal/Sources/HeartbeatLogging/Storage.swift

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import Foundation
1616

1717
/// A type that reads from and writes to an underlying storage container.
18-
protocol Storage {
18+
protocol Storage: Sendable {
1919
/// Reads and returns the data stored by this storage type.
2020
/// - Returns: The data read from storage.
2121
/// - Throws: An error if the read failed.
@@ -38,16 +38,12 @@ enum StorageError: Error {
3838
final class FileStorage: Storage {
3939
/// A file system URL to the underlying file resource.
4040
private let url: URL
41-
/// The file manager used to perform file system operations.
42-
private let fileManager: FileManager
4341

4442
/// Designated initializer.
4543
/// - Parameters:
4644
/// - url: A file system URL for the underlying file resource.
47-
/// - fileManager: A file manager. Defaults to `default` manager.
48-
init(url: URL, fileManager: FileManager = .default) {
45+
init(url: URL) {
4946
self.url = url
50-
self.fileManager = fileManager
5147
}
5248

5349
/// Reads and returns the data from this object's associated file resource.
@@ -90,7 +86,7 @@ final class FileStorage: Storage {
9086
/// - Parameter url: The URL to create directories in.
9187
private func createDirectories(in url: URL) throws {
9288
do {
93-
try fileManager.createDirectory(
89+
try FileManager.default.createDirectory(
9490
at: url,
9591
withIntermediateDirectories: true
9692
)
@@ -104,17 +100,26 @@ final class FileStorage: Storage {
104100

105101
/// A object that provides API for reading and writing to a user defaults resource.
106102
final class UserDefaultsStorage: Storage {
107-
/// The underlying defaults container.
108-
private let defaults: UserDefaults
103+
/// The suite name for the underlying defaults container.
104+
private let suiteName: String
105+
109106
/// The key mapping to the object's associated resource in `defaults`.
110107
private let key: String
111108

109+
/// The underlying defaults container.
110+
private var defaults: UserDefaults {
111+
// It's safe to force unwrap the below defaults instance because the
112+
// initializer only returns `nil` when the bundle id or `globalDomain`
113+
// is passed in as the `suiteName`.
114+
UserDefaults(suiteName: suiteName)!
115+
}
116+
112117
/// Designated initializer.
113118
/// - Parameters:
114-
/// - defaults: The defaults container.
119+
/// - suiteName: The suite name for the defaults container.
115120
/// - key: The key mapping to the value stored in the defaults container.
116-
init(defaults: UserDefaults, key: String) {
117-
self.defaults = defaults
121+
init(suiteName: String, key: String) {
122+
self.suiteName = suiteName
118123
self.key = key
119124
}
120125

FirebaseCore/Internal/Sources/HeartbeatLogging/StorageFactory.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,7 @@ extension FileManager {
5656
extension UserDefaultsStorage: StorageFactory {
5757
static func makeStorage(id: String) -> Storage {
5858
let suiteName = Constants.heartbeatUserDefaultsSuiteName
59-
// It's safe to force unwrap the below defaults instance because the
60-
// initializer only returns `nil` when the bundle id or `globalDomain`
61-
// is passed in as the `suiteName`.
62-
let defaults = UserDefaults(suiteName: suiteName)!
6359
let key = "heartbeats-\(id)"
64-
return UserDefaultsStorage(defaults: defaults, key: key)
60+
return UserDefaultsStorage(suiteName: suiteName, key: key)
6561
}
6662
}

FirebaseCore/Internal/Tests/Unit/StorageTests.swift

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,23 @@ class FileStorageTests: XCTestCase {
9797

9898
class UserDefaultsStorageTests: XCTestCase {
9999
var defaults: UserDefaults!
100-
let suiteName = #file
100+
let suiteName = "com.firebase.userdefaults.storageTests"
101101

102102
override func setUpWithError() throws {
103-
defaults = try XCTUnwrap(UserDefaultsFake(suiteName: suiteName))
103+
// Clear the user default suite before testing.
104+
UserDefaults(suiteName: suiteName)?.removePersistentDomain(forName: suiteName)
104105
}
105106

106107
func testRead_WhenDefaultDoesNotExist_ThrowsError() throws {
107108
// Given
108-
let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function)
109+
let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function)
109110
// Then
110111
XCTAssertThrowsError(try defaultsStorage.read())
111112
}
112113

113114
func testRead_WhenDefaultExists_ReturnsDefault() throws {
114115
// Given
115-
let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function)
116+
let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function)
116117
XCTAssertNoThrow(try defaultsStorage.write(Constants.testData))
117118
// When
118119
let storedData = try defaultsStorage.read()
@@ -122,7 +123,7 @@ class UserDefaultsStorageTests: XCTestCase {
122123

123124
func testWriteData_WhenDefaultDoesNotExist_CreatesDefault() throws {
124125
// Given
125-
let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function)
126+
let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function)
126127
XCTAssertThrowsError(try defaultsStorage.read())
127128
// When
128129
XCTAssertNoThrow(try defaultsStorage.write(Constants.testData))
@@ -133,7 +134,7 @@ class UserDefaultsStorageTests: XCTestCase {
133134

134135
func testWriteData_WhenDefaultExists_ModifiesDefault() throws {
135136
// Given
136-
let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function)
137+
let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function)
137138
XCTAssertNoThrow(try defaultsStorage.write(Constants.testData))
138139
// When
139140
let modifiedData = #function.data(using: .utf8)
@@ -146,7 +147,7 @@ class UserDefaultsStorageTests: XCTestCase {
146147

147148
func testWriteNil_WhenDefaultDoesNotExist_RemovesDefault() throws {
148149
// Given
149-
let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function)
150+
let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function)
150151
XCTAssertThrowsError(try defaultsStorage.read())
151152
// When
152153
XCTAssertNoThrow(try defaultsStorage.write(nil))
@@ -156,25 +157,11 @@ class UserDefaultsStorageTests: XCTestCase {
156157

157158
func testWriteNil_WhenDefaultExists_RemovesDefault() throws {
158159
// Given
159-
let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function)
160+
let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function)
160161
XCTAssertNoThrow(try defaultsStorage.write(Constants.testData))
161162
// When
162163
XCTAssertNoThrow(try defaultsStorage.write(nil))
163164
// Then
164165
XCTAssertThrowsError(try defaultsStorage.read())
165166
}
166167
}
167-
168-
// MARK: - Fakes
169-
170-
private class UserDefaultsFake: UserDefaults {
171-
private var defaults = [String: Any]()
172-
173-
override func object(forKey defaultName: String) -> Any? {
174-
defaults[defaultName]
175-
}
176-
177-
override func set(_ value: Any?, forKey defaultName: String) {
178-
defaults[defaultName] = value
179-
}
180-
}

0 commit comments

Comments
 (0)