Skip to content

Commit f4168b1

Browse files
author
Brian Meek
authored
perf(messaging, ios): Improve time to delivery of background messages on iOS (#5547)
* Defer callback event ‘messaging_message_received_background’ until the RN bridge is initialized and the javascript code has called setBackgroundMessageHandler * Timeout tasks waiting on the client to register with setBackgroundMessageHandler
1 parent 61aac51 commit f4168b1

File tree

5 files changed

+197
-128
lines changed

5 files changed

+197
-128
lines changed

packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,16 @@ NS_ASSUME_NONNULL_BEGIN
2525

2626
@property _Nullable RCTPromiseRejectBlock registerPromiseRejecter;
2727
@property _Nullable RCTPromiseResolveBlock registerPromiseResolver;
28+
@property (nonatomic, strong) NSCondition *conditionBackgroundMessageHandlerSet;
29+
@property (nonatomic) BOOL backgroundMessageHandlerSet;
30+
2831

2932
+ (_Nonnull instancetype)sharedInstance;
3033

3134
- (void)observe;
3235

36+
- (void)signalBackgroundMessageHandlerSet;
37+
3338
- (void)setPromiseResolve:(RCTPromiseResolveBlock)resolve andPromiseReject:(RCTPromiseRejectBlock)reject;
3439

3540
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;

packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ + (instancetype)sharedInstance {
3333
__strong static RNFBMessagingAppDelegate *sharedInstance;
3434
dispatch_once(&once, ^{
3535
sharedInstance = [[RNFBMessagingAppDelegate alloc] init];
36+
sharedInstance.conditionBackgroundMessageHandlerSet = [[NSCondition alloc] init];
37+
sharedInstance.backgroundMessageHandlerSet = NO;
3638
});
3739
return sharedInstance;
3840
}
@@ -66,6 +68,16 @@ - (void)observe {
6668
});
6769
}
6870

71+
// used to signal that a javascript handler for background messages is set
72+
- (void)signalBackgroundMessageHandlerSet {
73+
RNFBMessagingAppDelegate *sharedInstance = [RNFBMessagingAppDelegate sharedInstance];
74+
[sharedInstance.conditionBackgroundMessageHandlerSet lock];
75+
DLog(@"signalBackgroundMessageHandlerSet sharedInstance.backgroundMessageHandlerSet was %@", sharedInstance.backgroundMessageHandlerSet ? @"YES" : @"NO");
76+
sharedInstance.backgroundMessageHandlerSet = YES;
77+
[sharedInstance.conditionBackgroundMessageHandlerSet broadcast];
78+
[sharedInstance.conditionBackgroundMessageHandlerSet unlock];
79+
}
80+
6981
// used to temporarily store a promise instance to resolve calls to `registerForRemoteNotifications`
7082
- (void)setPromiseResolve:(RCTPromiseResolveBlock)resolve andPromiseReject:(RCTPromiseRejectBlock)reject {
7183
_registerPromiseResolver = resolve;
@@ -102,6 +114,7 @@ - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotif
102114
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
103115
#if __has_include(<FirebaseAuth/FirebaseAuth.h>)
104116
if ([[FIRAuth auth] canHandleNotification:userInfo]) {
117+
DLog(@"didReceiveRemoteNotification Firebase Auth handeled the notification");
105118
completionHandler(UIBackgroundFetchResultNoData);
106119
return;
107120
}
@@ -110,6 +123,8 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(N
110123
[[NSNotificationCenter defaultCenter] postNotificationName:@"RNFBMessagingDidReceiveRemoteNotification" object:userInfo];
111124

112125
if (userInfo[@"gcm.message_id"]) {
126+
DLog(@"didReceiveRemoteNotification gcm.message_id was present %@", userInfo);
127+
113128
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
114129
// If app is in background state, register background task to guarantee async queues aren't frozen.
115130
UIBackgroundTaskIdentifier __block backgroundTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
@@ -129,13 +144,46 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(N
129144
}
130145
});
131146

132-
// TODO investigate later - RN bridge gets invalidated at start when in background and a new bridge created - losing all events
133-
// TODO so we just delay sending the event for a few seconds as a workaround
134-
// TODO most likely Remote Debugging causing bridge to be invalidated
135-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
136-
[[RNFBRCTEventEmitter shared] sendEventWithName:@"messaging_message_received_background" body:[RNFBMessagingSerializer remoteMessageUserInfoToDict:userInfo]];
137-
});
138-
} else {
147+
RNFBMessagingAppDelegate *sharedInstance = [RNFBMessagingAppDelegate sharedInstance];
148+
[sharedInstance.conditionBackgroundMessageHandlerSet lock];
149+
@try {
150+
DLog(@"didReceiveRemoteNotification sharedInstance.backgroundMessageHandlerSet = %@", sharedInstance.backgroundMessageHandlerSet ? @"YES" : @"NO");
151+
if (sharedInstance.backgroundMessageHandlerSet) {
152+
// Normal path, backgroundMessageHandlerSet has already been set, queue the notification for immediate delivery
153+
dispatch_async(dispatch_get_main_queue(), ^{
154+
[[RNFBRCTEventEmitter shared] sendEventWithName:@"messaging_message_received_background" body:[RNFBMessagingSerializer remoteMessageUserInfoToDict:userInfo]];
155+
});
156+
DLog(@"didReceiveRemoteNotification without waiting for backgroundMessageHandlerSet to be set");
157+
} else {
158+
// This spin needs to be on a background/concurrent queue to await the setup of backgroundMessageHandlerSet and not block the main thread
159+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
160+
// Reaquire the lock in this new closure
161+
[sharedInstance.conditionBackgroundMessageHandlerSet lock];
162+
@try {
163+
// Spin/wait until backgroundMessageHandlerSet
164+
// NB it is possible while this closure was being scheduled that backgroundMessageHandlerSet is already set and this loop is skipped
165+
while (!sharedInstance.backgroundMessageHandlerSet) {
166+
DLog(@"didReceiveRemoteNotification waiting for sharedInstance.backgroundMessageHandlerSet %@", sharedInstance.backgroundMessageHandlerSet ? @"YES" : @"NO");
167+
if(![sharedInstance.conditionBackgroundMessageHandlerSet waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:25]]) {
168+
// If after 25 seconds the client hasn't called backgroundMessageHandlerSet, give up on this notification
169+
ELog(@"didReceiveRemoteNotification timed out waiting for sharedInstance.backgroundMessageHandlerSet");
170+
return;
171+
}
172+
}
173+
dispatch_async(dispatch_get_main_queue(), ^{
174+
[[RNFBRCTEventEmitter shared] sendEventWithName:@"messaging_message_received_background" body:[RNFBMessagingSerializer remoteMessageUserInfoToDict:userInfo]];
175+
});
176+
DLog(@"didReceiveRemoteNotification after waiting for backgroundMessageHandlerSet");
177+
} @finally {
178+
[sharedInstance.conditionBackgroundMessageHandlerSet unlock];
179+
}
180+
});
181+
}
182+
} @finally {
183+
[sharedInstance.conditionBackgroundMessageHandlerSet unlock];
184+
}
185+
} else {
186+
DLog(@"didReceiveRemoteNotification while app was in foreground");
139187
[[RNFBRCTEventEmitter shared] sendEventWithName:@"messaging_message_received" body:[RNFBMessagingSerializer remoteMessageUserInfoToDict:userInfo]];
140188
completionHandler(UIBackgroundFetchResultNoData);
141189
}

packages/messaging/ios/RNFBMessaging/RNFBMessagingModule.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ - (NSDictionary *)constantsToExport {
8989
return resolve([NSNull null]);
9090
}
9191

92+
RCT_EXPORT_METHOD(signalBackgroundMessageHandlerSet) {
93+
DLog(@"signalBackgroundMessageHandlerSet called");
94+
@try {
95+
[[RNFBMessagingAppDelegate sharedInstance] signalBackgroundMessageHandlerSet];
96+
} @catch (NSException *exception) {
97+
ELog(@"signalBackgroundMessageHandlerSet failed");
98+
}
99+
}
100+
92101
RCT_EXPORT_METHOD(getToken:
93102
(RCTPromiseResolveBlock) resolve
94103
:(RCTPromiseRejectBlock) reject

packages/messaging/lib/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@ class FirebaseMessagingModule extends FirebaseModule {
288288
}
289289

290290
/**
291-
* @platform android
291+
* Set a handler that will be called when a message is received while the app is in the background.
292+
* Should be called before the app is registered in `AppRegistry`, for example in `index.js`.
293+
* An app is considered to be in the background if no active window is displayed.
294+
* @param handler called with an argument of type messaging.RemoteMessage that must be async and return a Promise
292295
*/
293296
setBackgroundMessageHandler(handler) {
294297
if (!isFunction(handler)) {
@@ -298,6 +301,9 @@ class FirebaseMessagingModule extends FirebaseModule {
298301
}
299302

300303
backgroundMessageHandler = handler;
304+
if (isIOS) {
305+
this.native.signalBackgroundMessageHandlerSet();
306+
}
301307
}
302308

303309
sendMessage(remoteMessage) {

0 commit comments

Comments
 (0)