Skip to content

Commit c8de4fc

Browse files
authored
FCM: Fix crash on iOS 14 when multiple app delegate completion methods are called (#6863)
* Fix crash on iOS 14 when multiple completion methods are called * Fixed lint * Addressed the PR comments & implemented unit tests * Reverted auto-extracted method * Re-organized tests * Added completion handler for FIRAuth
1 parent 945d9d1 commit c8de4fc

File tree

4 files changed

+157
-5
lines changed

4 files changed

+157
-5
lines changed

FirebaseAuth/Sources/Auth/FIRAuth.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,7 @@ - (void)application:(UIApplication *)application
14991499
didReceiveRemoteNotification:(NSDictionary *)userInfo
15001500
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
15011501
[self canHandleNotification:userInfo];
1502+
completionHandler(UIBackgroundFetchResultNoData);
15021503
}
15031504

15041505
// iOS 10 deprecation

FirebaseMessaging/Sources/FIRMessagingRemoteNotificationsProxy.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ - (void)application:(UIApplication *)application
397397
didReceiveRemoteNotification:(NSDictionary *)userInfo
398398
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
399399
[[FIRMessaging messaging] appDidReceiveMessage:userInfo];
400+
completionHandler(UIBackgroundFetchResultNoData);
400401
}
401402

402403
- (void)application:(UIApplication *)application

GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
2222
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"
2323

24+
#import <dispatch/group.h>
2425
#import <objc/runtime.h>
2526

2627
// Implementations need to be typed before calling the implementation directly to cast the
@@ -882,24 +883,72 @@ - (void)application:(GULApplication *)application
882883
didReceiveRemoteNotificationWithCompletionIMP =
883884
[didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue];
884885

886+
dispatch_group_t __block callbackGroup = dispatch_group_create();
887+
NSMutableArray<NSNumber *> *__block fetchResults = [NSMutableArray array];
888+
889+
void (^localCompletionHandler)(UIBackgroundFetchResult) =
890+
^void(UIBackgroundFetchResult fetchResult) {
891+
[fetchResults addObject:[NSNumber numberWithInt:(int)fetchResult]];
892+
dispatch_group_leave(callbackGroup);
893+
};
894+
885895
// Notify interceptors.
886896
[GULAppDelegateSwizzler
887897
notifyInterceptorsWithMethodSelector:methodSelector
888898
callback:^(id<GULApplicationDelegate> interceptor) {
899+
dispatch_group_enter(callbackGroup);
900+
889901
NSInvocation *invocation = [GULAppDelegateSwizzler
890902
appDelegateInvocationForSelector:methodSelector];
891903
[invocation setTarget:interceptor];
892904
[invocation setSelector:methodSelector];
893905
[invocation setArgument:(void *)(&application) atIndex:2];
894906
[invocation setArgument:(void *)(&userInfo) atIndex:3];
895-
[invocation setArgument:(void *)(&completionHandler) atIndex:4];
907+
[invocation setArgument:(void *)(&localCompletionHandler)
908+
atIndex:4];
896909
[invocation invoke];
897910
}];
898911
// Call the real implementation if the real App Delegate has any.
899912
if (didReceiveRemoteNotificationWithCompletionIMP) {
913+
dispatch_group_enter(callbackGroup);
914+
900915
didReceiveRemoteNotificationWithCompletionIMP(self, methodSelector, application, userInfo,
901-
completionHandler);
916+
localCompletionHandler);
902917
}
918+
919+
dispatch_group_notify(callbackGroup, dispatch_get_main_queue(), ^() {
920+
BOOL allFetchesFailed = YES;
921+
BOOL anyFetchHasNewData = NO;
922+
923+
for (NSNumber *oneResult in fetchResults) {
924+
UIBackgroundFetchResult result = oneResult.intValue;
925+
926+
switch (result) {
927+
case UIBackgroundFetchResultNoData:
928+
allFetchesFailed = NO;
929+
break;
930+
case UIBackgroundFetchResultNewData:
931+
allFetchesFailed = NO;
932+
anyFetchHasNewData = YES;
933+
break;
934+
case UIBackgroundFetchResultFailed:
935+
936+
break;
937+
}
938+
}
939+
940+
UIBackgroundFetchResult finalFetchResult = UIBackgroundFetchResultNoData;
941+
942+
if (allFetchesFailed) {
943+
finalFetchResult = UIBackgroundFetchResultFailed;
944+
} else if (anyFetchHasNewData) {
945+
finalFetchResult = UIBackgroundFetchResultNewData;
946+
} else {
947+
finalFetchResult = UIBackgroundFetchResultNoData;
948+
}
949+
950+
completionHandler(finalFetchResult);
951+
});
903952
}
904953
#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX
905954

GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,12 +1065,12 @@ - (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInte
10651065
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
10661066
OCMExpect([interceptor application:application
10671067
didReceiveRemoteNotification:notification
1068-
fetchCompletionHandler:completion]);
1068+
fetchCompletionHandler:[OCMArg isNotNil]]);
10691069

10701070
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
10711071
OCMExpect([interceptor2 application:application
10721072
didReceiveRemoteNotification:notification
1073-
fetchCompletionHandler:completion]);
1073+
fetchCompletionHandler:[OCMArg isNotNil]]);
10741074

10751075
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
10761076
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
@@ -1087,7 +1087,108 @@ - (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInte
10871087

10881088
XCTAssertEqual(testAppDelegate.application, application);
10891089
XCTAssertEqual(testAppDelegate.remoteNotification, notification);
1090-
XCTAssertEqual(testAppDelegate.remoteNotificationCompletionHandler, completion);
1090+
}
1091+
1092+
- (void)verifyCompletionCalledForObserverResult:(UIBackgroundFetchResult)observerResult1
1093+
anotherObserverResult:(UIBackgroundFetchResult)observerResult2
1094+
swizzledResult:(UIBackgroundFetchResult)swizzledResult
1095+
expecredResult:(UIBackgroundFetchResult)expectedResult {
1096+
NSDictionary *notification = @{};
1097+
GULApplication *application = [GULApplication sharedApplication];
1098+
1099+
XCTestExpectation *completionExpectation =
1100+
[[XCTestExpectation alloc] initWithDescription:@"Completion called once"];
1101+
1102+
void (^completion)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
1103+
XCTAssertEqual(result, expectedResult);
1104+
[completionExpectation fulfill];
1105+
};
1106+
1107+
void (^onDidReceiveRemoteNotification1)(NSInvocation *invocation) = ^(NSInvocation *invocation) {
1108+
void __unsafe_unretained (^localCompletionHandler)(UIBackgroundFetchResult) = nil;
1109+
[invocation getArgument:(void *)(&localCompletionHandler) atIndex:4];
1110+
XCTAssertNotNil(localCompletionHandler);
1111+
localCompletionHandler(observerResult1);
1112+
};
1113+
1114+
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
1115+
OCMExpect([interceptor application:application
1116+
didReceiveRemoteNotification:notification
1117+
fetchCompletionHandler:[OCMArg isNotNil]])
1118+
.andDo(onDidReceiveRemoteNotification1);
1119+
1120+
void (^onDidReceiveRemoteNotification2)(NSInvocation *invocation) = ^(NSInvocation *invocation) {
1121+
void __unsafe_unretained (^localCompletionHandler)(UIBackgroundFetchResult) = nil;
1122+
[invocation getArgument:(void *)(&localCompletionHandler) atIndex:4];
1123+
XCTAssertNotNil(localCompletionHandler);
1124+
localCompletionHandler(observerResult2);
1125+
};
1126+
1127+
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
1128+
OCMExpect([interceptor2 application:application
1129+
didReceiveRemoteNotification:notification
1130+
fetchCompletionHandler:[OCMArg isNotNil]])
1131+
.andDo(onDidReceiveRemoteNotification2);
1132+
1133+
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
1134+
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
1135+
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
1136+
1137+
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
1138+
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
1139+
1140+
[testAppDelegate application:application
1141+
didReceiveRemoteNotification:notification
1142+
fetchCompletionHandler:completion];
1143+
testAppDelegate.remoteNotificationCompletionHandler(swizzledResult);
1144+
OCMVerifyAll(interceptor);
1145+
OCMVerifyAll(interceptor2);
1146+
[self waitForExpectations:@[ completionExpectation ] timeout:0.1];
1147+
}
1148+
1149+
- (void)testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce {
1150+
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNoData
1151+
anotherObserverResult:UIBackgroundFetchResultNoData
1152+
swizzledResult:UIBackgroundFetchResultNoData
1153+
expecredResult:UIBackgroundFetchResultNoData];
1154+
}
1155+
1156+
- (void)
1157+
testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleFailedState {
1158+
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultFailed
1159+
anotherObserverResult:UIBackgroundFetchResultFailed
1160+
swizzledResult:UIBackgroundFetchResultFailed
1161+
expecredResult:UIBackgroundFetchResultFailed];
1162+
}
1163+
1164+
- (void)testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_NoData {
1165+
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNoData
1166+
anotherObserverResult:UIBackgroundFetchResultFailed
1167+
swizzledResult:UIBackgroundFetchResultFailed
1168+
expecredResult:UIBackgroundFetchResultNoData];
1169+
}
1170+
- (void)
1171+
testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersFailed {
1172+
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData
1173+
anotherObserverResult:UIBackgroundFetchResultFailed
1174+
swizzledResult:UIBackgroundFetchResultFailed
1175+
expecredResult:UIBackgroundFetchResultNewData];
1176+
}
1177+
1178+
- (void)
1179+
testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersNoData {
1180+
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData
1181+
anotherObserverResult:UIBackgroundFetchResultNoData
1182+
swizzledResult:UIBackgroundFetchResultNoData
1183+
expecredResult:UIBackgroundFetchResultNewData];
1184+
}
1185+
1186+
- (void)
1187+
testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersNoDataFailed {
1188+
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData
1189+
anotherObserverResult:UIBackgroundFetchResultNoData
1190+
swizzledResult:UIBackgroundFetchResultFailed
1191+
expecredResult:UIBackgroundFetchResultNewData];
10911192
}
10921193

10931194
- (void)testApplicationDidReceiveRemoteNotificationWithCompletionImplementationIsNotAdded {

0 commit comments

Comments
 (0)