diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift index 754fa761a54..7180313df34 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift @@ -534,6 +534,11 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { #if COCOAPODS || SWIFT_PACKAGE private class FakeHeartbeatLogger: NSObject, FIRHeartbeatLoggerProtocol { + func headerValue() -> String? { + // `asyncHeaderValue` should be used instead. + fatalError("FakeHeartbeatLogger headerValue should not be used in tests.") + } + func asyncHeaderValue() async -> String? { let payload = flushHeartbeatsIntoPayload() guard !payload.isEmpty else { diff --git a/FirebaseCore/CHANGELOG.md b/FirebaseCore/CHANGELOG.md index 16a6b9d1290..89ed80fcf55 100644 --- a/FirebaseCore/CHANGELOG.md +++ b/FirebaseCore/CHANGELOG.md @@ -1,3 +1,11 @@ +# Firebase 11.4.2 +- [fixed] CocoaPods only release to fix iOS 12 build failure resulting from + incomplete implementation in the FirebaseCoreInternal CocoaPod. + +# Firebase 11.4.1 +- [fixed] CocoaPods only release to revert breaking change in + `FirebaseCoreExtension` SDK. (#13942) + # Firebase 11.4.0 - [fixed] Fixed issue building documentation with some Firebase products. (#13756) diff --git a/FirebaseCore/Extension/FIRHeartbeatLogger.h b/FirebaseCore/Extension/FIRHeartbeatLogger.h index 12986f8ec97..962974e1bca 100644 --- a/FirebaseCore/Extension/FIRHeartbeatLogger.h +++ b/FirebaseCore/Extension/FIRHeartbeatLogger.h @@ -44,8 +44,7 @@ typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) { API_AVAILABLE(ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)); /// Return the header value for the heartbeat logger. -- (NSString *_Nullable) - headerValue NS_SWIFT_UNAVAILABLE("Use `asyncHeaderValue() async -> String?` instead."); +- (NSString *_Nullable)headerValue; #endif // FIREBASE_BUILD_CMAKE @end diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift index e4a2cc4c335..e7f4ece2781 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift @@ -126,34 +126,30 @@ public final class HeartbeatController { } } - @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) - public func flushAsync() async -> HeartbeatsPayload { - return await withCheckedContinuation { continuation in - let resetTransform = { (heartbeatsBundle: HeartbeatsBundle?) -> HeartbeatsBundle? in - guard let oldHeartbeatsBundle = heartbeatsBundle else { - return nil // Storage was empty. - } - // The new value that's stored will use the old's cache to prevent the - // logging of duplicates after flushing. - return HeartbeatsBundle( - capacity: self.heartbeatsStorageCapacity, - cache: oldHeartbeatsBundle.lastAddedHeartbeatDates - ) + public func flushAsync(completionHandler: @escaping (HeartbeatsPayload) -> Void) { + let resetTransform = { (heartbeatsBundle: HeartbeatsBundle?) -> HeartbeatsBundle? in + guard let oldHeartbeatsBundle = heartbeatsBundle else { + return nil // Storage was empty. } + // The new value that's stored will use the old's cache to prevent the + // logging of duplicates after flushing. + return HeartbeatsBundle( + capacity: self.heartbeatsStorageCapacity, + cache: oldHeartbeatsBundle.lastAddedHeartbeatDates + ) + } - // Asynchronously gets and returns the stored heartbeats, resetting storage - // using the given transform. - storage.getAndSetAsync(using: resetTransform) { result in - switch result { - case let .success(heartbeatsBundle): - // If no heartbeats bundle was stored, return an empty payload. - continuation - .resume(returning: heartbeatsBundle?.makeHeartbeatsPayload() ?? HeartbeatsPayload - .emptyPayload) - case .failure: - // If the operation throws, assume no heartbeat(s) were retrieved or set. - continuation.resume(returning: HeartbeatsPayload.emptyPayload) - } + // Asynchronously gets and returns the stored heartbeats, resetting storage + // using the given transform. + storage.getAndSetAsync(using: resetTransform) { result in + switch result { + case let .success(heartbeatsBundle): + // If no heartbeats bundle was stored, return an empty payload. + completionHandler(heartbeatsBundle?.makeHeartbeatsPayload() ?? HeartbeatsPayload + .emptyPayload) + case .failure: + // If the operation throws, assume no heartbeat(s) were retrieved or set. + completionHandler(HeartbeatsPayload.emptyPayload) } } } diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift index fdd13af13be..c60a1e11cc5 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift @@ -49,10 +49,12 @@ public class _ObjC_HeartbeatController: NSObject { /// /// - Note: This API is thread-safe. /// - Returns: A heartbeats payload for the flushed heartbeat(s). - @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) - public func flushAsync() async -> _ObjC_HeartbeatsPayload { - let heartbeatsPayload = await heartbeatController.flushAsync() - return _ObjC_HeartbeatsPayload(heartbeatsPayload) + public func flushAsync(completionHandler: @escaping (_ObjC_HeartbeatsPayload) -> Void) { + // TODO: When minimum version moves to iOS 13.0, restore the async version + // removed in #13952. + heartbeatController.flushAsync { heartbeatsPayload in + completionHandler(_ObjC_HeartbeatsPayload(heartbeatsPayload)) + } } /// Synchronously flushes the heartbeat for today. diff --git a/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift b/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift index b306405f4bd..3a9823aa94a 100644 --- a/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift +++ b/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift @@ -52,29 +52,36 @@ class HeartbeatLoggingIntegrationTests: XCTestCase { ) } - @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) - func testLogAndFlushAsync() async throws { + func testLogAndFlushAsync() throws { // Given let heartbeatController = HeartbeatController(id: #function) let expectedDate = HeartbeatsPayload.dateFormatter.string(from: Date()) + let expectation = self.expectation(description: #function) // When heartbeatController.log("dummy_agent") - let payload = await heartbeatController.flushAsync() - // Then - try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( - payload.headerValue(), - """ - { - "version": 2, - "heartbeats": [ + heartbeatController.flushAsync { payload in + // Then + do { + try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( + payload.headerValue(), + """ { - "agent": "dummy_agent", - "dates": ["\(expectedDate)"] + "version": 2, + "heartbeats": [ + { + "agent": "dummy_agent", + "dates": ["\(expectedDate)"] + } + ] } - ] + """ + ) + expectation.fulfill() + } catch { + XCTFail("Unexpected error: \(error)") } - """ - ) + } + waitForExpectations(timeout: 1.0) } /// This test may flake if it is executed during the transition from one day to the next. diff --git a/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift b/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift index ddf3d1c5d9d..82691ed3f24 100644 --- a/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift @@ -58,35 +58,41 @@ class HeartbeatControllerTests: XCTestCase { assertHeartbeatControllerFlushesEmptyPayload(controller) } - @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) - func testLogAndFlushAsync() async throws { + func testLogAndFlushAsync() throws { // Given let controller = HeartbeatController( storage: HeartbeatStorageFake(), dateProvider: { self.date } ) + let expectation = expectation(description: #function) assertHeartbeatControllerFlushesEmptyPayload(controller) // When controller.log("dummy_agent") - let heartbeatPayload = await controller.flushAsync() - - // Then - try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( - heartbeatPayload.headerValue(), - """ - { - "version": 2, - "heartbeats": [ + controller.flushAsync { heartbeatPayload in + // Then + do { + try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( + heartbeatPayload.headerValue(), + """ { - "agent": "dummy_agent", - "dates": ["2021-11-01"] + "version": 2, + "heartbeats": [ + { + "agent": "dummy_agent", + "dates": ["2021-11-01"] + } + ] } - ] + """ + ) + expectation.fulfill() + } catch { + XCTFail("Unexpected error: \(error)") } - """ - ) + } + waitForExpectations(timeout: 1.0) assertHeartbeatControllerFlushesEmptyPayload(controller) } diff --git a/FirebaseCore/Sources/FIRHeartbeatLogger.m b/FirebaseCore/Sources/FIRHeartbeatLogger.m index d1c77ee4f2f..4becd085c22 100644 --- a/FirebaseCore/Sources/FIRHeartbeatLogger.m +++ b/FirebaseCore/Sources/FIRHeartbeatLogger.m @@ -87,7 +87,8 @@ - (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload { } - (void)flushHeartbeatsIntoPayloadWithCompletionHandler: - (void (^)(FIRHeartbeatsPayload *))completionHandler { + (void (^)(FIRHeartbeatsPayload *))completionHandler + API_AVAILABLE(ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)) { [_heartbeatController flushAsyncWithCompletionHandler:^(FIRHeartbeatsPayload *payload) { completionHandler(payload); }];