diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift index 17bd7a30433..74472e33c71 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift @@ -105,22 +105,22 @@ import Foundation private extension ActionCodeSettings { /// Checked Sendable implementation of `ActionCodeSettings`. final class SendableActionCodeSettings: Sendable { - let url = FIRAllocatedUnfairLock(initialState: nil) + let url = UnfairLock(nil) - let handleCodeInApp = FIRAllocatedUnfairLock(initialState: false) + let handleCodeInApp = UnfairLock(false) - let iOSBundleID: FIRAllocatedUnfairLock + let iOSBundleID: UnfairLock - let androidPackageName = FIRAllocatedUnfairLock(initialState: nil) + let androidPackageName = UnfairLock(nil) - let androidMinimumVersion = FIRAllocatedUnfairLock(initialState: nil) + let androidMinimumVersion = UnfairLock(nil) - let androidInstallIfNotAvailable = FIRAllocatedUnfairLock(initialState: false) + let androidInstallIfNotAvailable = UnfairLock(false) - let linkDomain = FIRAllocatedUnfairLock(initialState: nil) + let linkDomain = UnfairLock(nil) init() { - iOSBundleID = FIRAllocatedUnfairLock(initialState: Bundle.main.bundleIdentifier) + iOSBundleID = UnfairLock(Bundle.main.bundleIdentifier) } func setAndroidPackageName(_ androidPackageName: String, diff --git a/FirebaseAuth/Sources/Swift/Storage/AuthKeychainServices.swift b/FirebaseAuth/Sources/Swift/Storage/AuthKeychainServices.swift index 349da7b73f8..fb3004da268 100644 --- a/FirebaseAuth/Sources/Swift/Storage/AuthKeychainServices.swift +++ b/FirebaseAuth/Sources/Swift/Storage/AuthKeychainServices.swift @@ -102,7 +102,7 @@ final class AuthKeychainServices: Sendable { /// been deleted. /// /// This dictionary is to avoid unnecessary keychain operations against legacy items. - private let legacyEntryDeletedForKey = FIRAllocatedUnfairLock>(initialState: []) + private let legacyEntryDeletedForKey = UnfairLock>([]) func data(forKey key: String) throws -> Data? { if let data = try getItemLegacy(query: genericPasswordQuery(key: key)) { diff --git a/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift b/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift index 34f52a60a28..223f06e8014 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift @@ -125,7 +125,7 @@ final class SecureTokenService: NSObject, NSSecureCoding, Sendable { set { _requestConfiguration.withLock { $0 = newValue } } } - let _requestConfiguration: FIRAllocatedUnfairLock + let _requestConfiguration: UnfairLock /// The cached access token. /// @@ -140,7 +140,7 @@ final class SecureTokenService: NSObject, NSSecureCoding, Sendable { set { _accessToken.withLock { $0 = newValue } } } - private let _accessToken: FIRAllocatedUnfairLock + private let _accessToken: UnfairLock /// The refresh token for the user, or `nil` if the user has yet completed sign-in flow. /// @@ -150,7 +150,7 @@ final class SecureTokenService: NSObject, NSSecureCoding, Sendable { set { _refreshToken.withLock { $0 = newValue } } } - private let _refreshToken: FIRAllocatedUnfairLock + private let _refreshToken: UnfairLock /// The expiration date of the cached access token. var accessTokenExpirationDate: Date? { @@ -158,7 +158,7 @@ final class SecureTokenService: NSObject, NSSecureCoding, Sendable { set { _accessTokenExpirationDate.withLock { $0 = newValue } } } - private let _accessTokenExpirationDate: FIRAllocatedUnfairLock + private let _accessTokenExpirationDate: UnfairLock /// Creates a `SecureTokenService` with access and refresh tokens. /// - Parameter requestConfiguration: The configuration for making requests to server. @@ -170,10 +170,10 @@ final class SecureTokenService: NSObject, NSSecureCoding, Sendable { accessTokenExpirationDate: Date?, refreshToken: String) { internalService = SecureTokenServiceInternal() - _requestConfiguration = FIRAllocatedUnfairLock(initialState: requestConfiguration) - _accessToken = FIRAllocatedUnfairLock(initialState: accessToken) - _accessTokenExpirationDate = FIRAllocatedUnfairLock(initialState: accessTokenExpirationDate) - _refreshToken = FIRAllocatedUnfairLock(initialState: refreshToken) + _requestConfiguration = UnfairLock(requestConfiguration) + _accessToken = UnfairLock(accessToken) + _accessTokenExpirationDate = UnfairLock(accessTokenExpirationDate) + _refreshToken = UnfairLock(refreshToken) } /// Fetch a fresh ephemeral access token for the ID associated with this instance. The token diff --git a/FirebaseAuth/Tests/Unit/AuthAPNSTokenManagerTests.swift b/FirebaseAuth/Tests/Unit/AuthAPNSTokenManagerTests.swift index 0dec3c5e4ce..4cf6f8f29a9 100644 --- a/FirebaseAuth/Tests/Unit/AuthAPNSTokenManagerTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthAPNSTokenManagerTests.swift @@ -62,7 +62,7 @@ func testCallback() throws { let expectation = self.expectation(description: #function) XCTAssertFalse(fakeApplication!.registerCalled) - let firstCallbackCalled = FIRAllocatedUnfairLock(initialState: false) + let firstCallbackCalled = UnfairLock(false) let manager = try XCTUnwrap(manager) manager.getTokenInternal { result in firstCallbackCalled.withLock { $0 = true } @@ -77,7 +77,7 @@ XCTAssertFalse(firstCallbackCalled.value()) // Add second callback, which is yet to be called either. - let secondCallbackCalled = FIRAllocatedUnfairLock(initialState: false) + let secondCallbackCalled = UnfairLock(false) manager.getTokenInternal { result in secondCallbackCalled.withLock { $0 = true } switch result { @@ -104,7 +104,7 @@ XCTAssertEqual(manager.token?.type, .sandbox) // Add third callback, which should be called back immediately. - let thirdCallbackCalled = FIRAllocatedUnfairLock(initialState: false) + let thirdCallbackCalled = UnfairLock(false) manager.getTokenInternal { result in thirdCallbackCalled.withLock { $0 = true } switch result { @@ -178,7 +178,7 @@ XCTAssertGreaterThan(try XCTUnwrap(manager.timeout), 0) // Add callback to cancel. - let callbackCalled = FIRAllocatedUnfairLock(initialState: false) + let callbackCalled = UnfairLock(false) manager.getTokenInternal { result in switch result { case let .success(token): diff --git a/FirebaseAuth/Tests/Unit/Fakes/FakeAuthKeychainStorage.swift b/FirebaseAuth/Tests/Unit/Fakes/FakeAuthKeychainStorage.swift index 251c1b719db..d40846b9882 100644 --- a/FirebaseAuth/Tests/Unit/Fakes/FakeAuthKeychainStorage.swift +++ b/FirebaseAuth/Tests/Unit/Fakes/FakeAuthKeychainStorage.swift @@ -21,9 +21,9 @@ import XCTest @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) final class FakeAuthKeychainStorage: AuthKeychainStorage { // Fake Keychain. It's a dictionary, keyed by service name, for each key-value store dictionary - private let fakeKeychain = FIRAllocatedUnfairLock<[String: [String: Any]]>(initialState: [:]) + private let fakeKeychain = UnfairLock<[String: [String: Any]]>([:]) - private let fakeLegacyKeychain = FIRAllocatedUnfairLock<[String: Any]>(initialState: [:]) + private let fakeLegacyKeychain = UnfairLock<[String: Any]>([:]) func get(query: [String: Any], result: inout AnyObject?) -> OSStatus { if let service = queryService(query) { diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift index 224426e086a..c99c55b4e7e 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift @@ -52,9 +52,9 @@ final class HeartbeatStorage: Sendable, HeartbeatStorageProtocol { // MARK: - Instance Management /// Statically allocated cache of `HeartbeatStorage` instances keyed by string IDs. - private static let cachedInstances: FIRAllocatedUnfairLock< + private static let cachedInstances: UnfairLock< [String: WeakContainer] - > = FIRAllocatedUnfairLock(initialState: [:]) + > = UnfairLock([:]) /// Gets an existing `HeartbeatStorage` instance with the given `id` if one exists. Otherwise, /// makes a new instance with the given `id`. diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/WeakContainer.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/WeakContainer.swift index f1dd1777aea..d034fce3c8c 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/WeakContainer.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/WeakContainer.swift @@ -18,3 +18,5 @@ import Foundation struct WeakContainer { weak var object: Object? } + +extension WeakContainer: Sendable where Object: Sendable {} diff --git a/FirebaseCore/Internal/Sources/Utilities/FIRAllocatedUnfairLock.swift b/FirebaseCore/Internal/Sources/Utilities/UnfairLock.swift similarity index 68% rename from FirebaseCore/Internal/Sources/Utilities/FIRAllocatedUnfairLock.swift rename to FirebaseCore/Internal/Sources/Utilities/UnfairLock.swift index c94f3153db9..9028029763c 100644 --- a/FirebaseCore/Internal/Sources/Utilities/FIRAllocatedUnfairLock.swift +++ b/FirebaseCore/Internal/Sources/Utilities/UnfairLock.swift @@ -13,60 +13,54 @@ // limitations under the License. import Foundation -import os.lock +private import os.lock /// A reference wrapper around `os_unfair_lock`. Replace this class with /// `OSAllocatedUnfairLock` once we support only iOS 16+. For an explanation /// on why this is necessary, see the docs: /// https://developer.apple.com/documentation/os/osallocatedunfairlock -public final class FIRAllocatedUnfairLock: @unchecked Sendable { +public final class UnfairLock: @unchecked Sendable { private var lockPointer: UnsafeMutablePointer - private var state: State + private var _value: Value - public init(initialState: sending State) { + public init(_ value: consuming sending Value) { lockPointer = UnsafeMutablePointer .allocate(capacity: 1) lockPointer.initialize(to: os_unfair_lock()) - state = initialState + _value = value } - public convenience init() where State == Void { - self.init(initialState: ()) - } - - public func lock() { - os_unfair_lock_lock(lockPointer) - } - - public func unlock() { - os_unfair_lock_unlock(lockPointer) + deinit { + lockPointer.deallocate() } - public func value() -> State { + public func value() -> Value { lock() defer { unlock() } - return state + return _value } @discardableResult - public func withLock(_ body: (inout State) throws -> R) rethrows -> R { - let value: R + public borrowing func withLock(_ body: (inout sending Value) throws + -> sending Result) rethrows -> sending Result { lock() defer { unlock() } - value = try body(&state) - return value + return try body(&_value) } @discardableResult - public func withLock(_ body: () throws -> R) rethrows -> R { - let value: R + public borrowing func withLock(_ body: (inout sending Value) -> sending Result) + -> sending Result { lock() defer { unlock() } - value = try body() - return value + return body(&_value) } - deinit { - lockPointer.deallocate() + private func lock() { + os_unfair_lock_lock(lockPointer) + } + + private func unlock() { + os_unfair_lock_unlock(lockPointer) } } diff --git a/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift b/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift index c48cea653f7..d8cec20a203 100644 --- a/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift @@ -407,7 +407,7 @@ class HeartbeatStorageTests: XCTestCase { final class WeakRefs: @unchecked Sendable { // Lock is used to synchronize `weakRefs` during concurrent access. private(set) var weakRefs = - FIRAllocatedUnfairLock<[WeakContainer]>(initialState: []) + UnfairLock<[WeakContainer]>([]) func append(_ weakRef: WeakContainer) { weakRefs.withLock { diff --git a/FirebaseFunctions/Sources/Functions.swift b/FirebaseFunctions/Sources/Functions.swift index 8ab6d63e780..42a59a3e106 100644 --- a/FirebaseFunctions/Sources/Functions.swift +++ b/FirebaseFunctions/Sources/Functions.swift @@ -53,7 +53,7 @@ enum FunctionsConstants { /// A map of active instances, grouped by app. Keys are FirebaseApp names and values are arrays /// containing all instances of Functions associated with the given app. - private static let instances = FIRAllocatedUnfairLock<[String: [Functions]]>(initialState: [:]) + private static let instances = UnfairLock<[String: [Functions]]>([:]) /// The custom domain to use for all functions references (optional). let customDomain: String? @@ -61,7 +61,7 @@ enum FunctionsConstants { /// The region to use for all function references. let region: String - private let _emulatorOrigin: FIRAllocatedUnfairLock + private let _emulatorOrigin: UnfairLock // MARK: - Public APIs @@ -341,7 +341,7 @@ enum FunctionsConstants { self.projectID = projectID self.region = region self.customDomain = customDomain - _emulatorOrigin = FIRAllocatedUnfairLock(initialState: nil) + _emulatorOrigin = UnfairLock(nil) contextProvider = FunctionsContextProvider(auth: auth, messaging: messaging, appCheck: appCheck) diff --git a/FirebaseFunctions/Sources/HTTPSCallable.swift b/FirebaseFunctions/Sources/HTTPSCallable.swift index b03d5b8fccd..b74e5b93c4b 100644 --- a/FirebaseFunctions/Sources/HTTPSCallable.swift +++ b/FirebaseFunctions/Sources/HTTPSCallable.swift @@ -43,7 +43,7 @@ public final class HTTPSCallable: NSObject, Sendable { private let options: HTTPSCallableOptions? - private let _timeoutInterval: AtomicBox = .init(70) + private let _timeoutInterval: UnfairLock = .init(70) // MARK: - Public Properties diff --git a/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift b/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift index 5a13dcfb838..158be0bee64 100644 --- a/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift +++ b/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift @@ -39,9 +39,9 @@ extension InstallationsProtocol { // TODO(ncooke3): Convert o async await ahead of Firebase 12. func installationID(completion: @escaping (Result<(String, String), Error>) -> Void) { - let authTokenComplete = FIRAllocatedUnfairLock(initialState: "") - let installationComplete = FIRAllocatedUnfairLock(initialState: nil) - let errorComplete = FIRAllocatedUnfairLock(initialState: nil) + let authTokenComplete = UnfairLock("") + let installationComplete = UnfairLock(nil) + let errorComplete = UnfairLock(nil) let workingGroup = DispatchGroup() diff --git a/FirebaseSessions/Sources/Public/SessionsDependencies.swift b/FirebaseSessions/Sources/Public/SessionsDependencies.swift index 9e0b3aafe6e..923bd1daea6 100644 --- a/FirebaseSessions/Sources/Public/SessionsDependencies.swift +++ b/FirebaseSessions/Sources/Public/SessionsDependencies.swift @@ -27,7 +27,7 @@ private import FirebaseCoreInternal @objc(FIRSessionsDependencies) public class SessionsDependencies: NSObject { private static let _dependencies = - FIRAllocatedUnfairLock>(initialState: Set()) + UnfairLock>(Set()) static var dependencies: Set { _dependencies.value() diff --git a/FirebaseSessions/Sources/Public/SessionsSubscriber.swift b/FirebaseSessions/Sources/Public/SessionsSubscriber.swift index bc15106e3c5..3f6225f81aa 100644 --- a/FirebaseSessions/Sources/Public/SessionsSubscriber.swift +++ b/FirebaseSessions/Sources/Public/SessionsSubscriber.swift @@ -27,8 +27,8 @@ public protocol SessionsSubscriber: Sendable { /// Session Payload is a container for Session Data passed to Subscribers /// whenever the Session changes @objc(FIRSessionDetails) -public class SessionDetails: NSObject { - @objc public var sessionId: String? +public final class SessionDetails: NSObject, Sendable { + @objc public let sessionId: String? public init(sessionId: String?) { self.sessionId = sessionId diff --git a/FirebaseSessions/Sources/Settings/RemoteSettings.swift b/FirebaseSessions/Sources/Settings/RemoteSettings.swift index 9f57b348b88..2e0e0080ab0 100644 --- a/FirebaseSessions/Sources/Settings/RemoteSettings.swift +++ b/FirebaseSessions/Sources/Settings/RemoteSettings.swift @@ -29,7 +29,7 @@ final class RemoteSettings: SettingsProvider, Sendable { private static let flagSessionsCache = "app_quality" private let appInfo: ApplicationInfoProtocol private let downloader: SettingsDownloadClient - private let cache: FIRAllocatedUnfairLock + private let cache: UnfairLock private var sessionsCache: [String: Any] { cache.withLock { cache in @@ -41,7 +41,7 @@ final class RemoteSettings: SettingsProvider, Sendable { downloader: SettingsDownloadClient, cache: SettingsCacheClient = SettingsCache()) { self.appInfo = appInfo - self.cache = FIRAllocatedUnfairLock(initialState: cache) + self.cache = UnfairLock(cache) self.downloader = downloader } diff --git a/FirebaseSessions/Tests/Unit/Mocks/MockSubscriber.swift b/FirebaseSessions/Tests/Unit/Mocks/MockSubscriber.swift index 2f3845e70bc..6c4238312f8 100644 --- a/FirebaseSessions/Tests/Unit/Mocks/MockSubscriber.swift +++ b/FirebaseSessions/Tests/Unit/Mocks/MockSubscriber.swift @@ -30,10 +30,8 @@ final class MockSubscriber: SessionsSubscriber, Sendable { set { _isDataCollectionEnabled.withLock { $0 = newValue } } } - private let _sessionThatChanged = FIRAllocatedUnfairLock( - initialState: nil - ) - private let _isDataCollectionEnabled = FIRAllocatedUnfairLock(initialState: true) + private let _sessionThatChanged = UnfairLock(nil) + private let _isDataCollectionEnabled = UnfairLock(true) init(name: SessionsSubscriberName) { sessionsSubscriberName = name