Flutter Engine
FlutterPluginAppLifeCycleDelegate.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
6 
7 #include "flutter/fml/logging.h"
8 #include "flutter/fml/paths.h"
9 #include "flutter/lib/ui/plugins/callback_cache.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h"
12 
13 static const char* kCallbackCacheSubDir = "Library/Caches/";
14 
15 static const SEL selectorsHandledByPlugins[] = {
16  @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:),
17  @selector(application:performFetchWithCompletionHandler:)};
18 
20 - (void)handleDidEnterBackground:(NSNotification*)notification;
21 - (void)handleWillEnterForeground:(NSNotification*)notification;
22 - (void)handleWillResignActive:(NSNotification*)notification;
23 - (void)handleDidBecomeActive:(NSNotification*)notification;
24 - (void)handleWillTerminate:(NSNotification*)notification;
25 @end
26 
28  NSMutableArray* _notificationUnsubscribers;
29  UIBackgroundTaskIdentifier _debugBackgroundTask;
30 
31  // Weak references to registered plugins.
32  NSPointerArray* _delegates;
33 }
34 
35 - (void)addObserverFor:(NSString*)name selector:(SEL)selector {
36  [[NSNotificationCenter defaultCenter] addObserver:self selector:selector name:name object:nil];
37  __block NSObject* blockSelf = self;
38  dispatch_block_t unsubscribe = ^{
39  [[NSNotificationCenter defaultCenter] removeObserver:blockSelf name:name object:nil];
40  };
41  [_notificationUnsubscribers addObject:[[unsubscribe copy] autorelease]];
42 }
43 
44 - (instancetype)init {
45  if (self = [super init]) {
46  _notificationUnsubscribers = [[NSMutableArray alloc] init];
47  std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
48  [FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
49  [self addObserverFor:UIApplicationDidEnterBackgroundNotification
50  selector:@selector(handleDidEnterBackground:)];
51  [self addObserverFor:UIApplicationWillEnterForegroundNotification
52  selector:@selector(handleWillEnterForeground:)];
53  [self addObserverFor:UIApplicationWillResignActiveNotification
54  selector:@selector(handleWillResignActive:)];
55  [self addObserverFor:UIApplicationDidBecomeActiveNotification
56  selector:@selector(handleDidBecomeActive:)];
57  [self addObserverFor:UIApplicationWillTerminateNotification
58  selector:@selector(handleWillTerminate:)];
59  _delegates = [[NSPointerArray weakObjectsPointerArray] retain];
60  _debugBackgroundTask = UIBackgroundTaskInvalid;
61  }
62  return self;
63 }
64 
65 - (void)dealloc {
66  for (dispatch_block_t unsubscribe in _notificationUnsubscribers) {
67  unsubscribe();
68  }
69  [_notificationUnsubscribers release];
70  [_delegates release];
71  [super dealloc];
72 }
73 
74 static BOOL isPowerOfTwo(NSUInteger x) {
75  return x != 0 && (x & (x - 1)) == 0;
76 }
77 
78 - (BOOL)isSelectorAddedDynamically:(SEL)selector {
79  for (const SEL& aSelector : selectorsHandledByPlugins) {
80  if (selector == aSelector) {
81  return YES;
82  }
83  }
84  return NO;
85 }
86 
87 - (BOOL)hasPluginThatRespondsToSelector:(SEL)selector {
88  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
89  if (!delegate) {
90  continue;
91  }
92  if ([delegate respondsToSelector:selector]) {
93  return YES;
94  }
95  }
96  return NO;
97 }
98 
99 - (void)addDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
100  [_delegates addPointer:(__bridge void*)delegate];
101  if (isPowerOfTwo([_delegates count])) {
102  [_delegates compact];
103  }
104 }
105 
106 - (BOOL)application:(UIApplication*)application
107  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
108  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
109  if (!delegate) {
110  continue;
111  }
112  if ([delegate respondsToSelector:_cmd]) {
113  if (![delegate application:application didFinishLaunchingWithOptions:launchOptions]) {
114  return NO;
115  }
116  }
117  }
118  return YES;
119 }
120 
121 - (BOOL)application:(UIApplication*)application
122  willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
124  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
125  if (!delegate) {
126  continue;
127  }
128  if ([delegate respondsToSelector:_cmd]) {
129  if (![delegate application:application willFinishLaunchingWithOptions:launchOptions]) {
130  return NO;
131  }
132  }
133  }
134  return YES;
135 }
136 
137 - (void)handleDidEnterBackground:(NSNotification*)notification {
138  UIApplication* application = [UIApplication sharedApplication];
139 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
140  // The following keeps the Flutter session alive when the device screen locks
141  // in debug mode. It allows continued use of features like hot reload and
142  // taking screenshots once the device unlocks again.
143  //
144  // Note the name is not an identifier and multiple instances can exist.
145  _debugBackgroundTask = [application
146  beginBackgroundTaskWithName:@"Flutter debug task"
147  expirationHandler:^{
148  if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
149  [application endBackgroundTask:_debugBackgroundTask];
150  _debugBackgroundTask = UIBackgroundTaskInvalid;
151  }
152  FML_LOG(WARNING)
153  << "\nThe OS has terminated the Flutter debug connection for being "
154  "inactive in the background for too long.\n\n"
155  "There are no errors with your Flutter application.\n\n"
156  "To reconnect, launch your application again via 'flutter run'";
157  }];
158 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
159  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
160  if (!delegate) {
161  continue;
162  }
163  if ([delegate respondsToSelector:@selector(applicationDidEnterBackground:)]) {
164  [delegate applicationDidEnterBackground:application];
165  }
166  }
167 }
168 
169 - (void)handleWillEnterForeground:(NSNotification*)notification {
170  UIApplication* application = [UIApplication sharedApplication];
171 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
172  if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
173  [application endBackgroundTask:_debugBackgroundTask];
174  _debugBackgroundTask = UIBackgroundTaskInvalid;
175  }
176 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
177  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
178  if (!delegate) {
179  continue;
180  }
181  if ([delegate respondsToSelector:@selector(applicationWillEnterForeground:)]) {
182  [delegate applicationWillEnterForeground:application];
183  }
184  }
185 }
186 
187 - (void)handleWillResignActive:(NSNotification*)notification {
188  UIApplication* application = [UIApplication sharedApplication];
189  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
190  if (!delegate) {
191  continue;
192  }
193  if ([delegate respondsToSelector:@selector(applicationWillResignActive:)]) {
194  [delegate applicationWillResignActive:application];
195  }
196  }
197 }
198 
199 - (void)handleDidBecomeActive:(NSNotification*)notification {
200  UIApplication* application = [UIApplication sharedApplication];
201  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
202  if (!delegate) {
203  continue;
204  }
205  if ([delegate respondsToSelector:@selector(applicationDidBecomeActive:)]) {
206  [delegate applicationDidBecomeActive:application];
207  }
208  }
209 }
210 
211 - (void)handleWillTerminate:(NSNotification*)notification {
212  UIApplication* application = [UIApplication sharedApplication];
213  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
214  if (!delegate) {
215  continue;
216  }
217  if ([delegate respondsToSelector:@selector(applicationWillTerminate:)]) {
218  [delegate applicationWillTerminate:application];
219  }
220  }
221 }
222 
223 #pragma GCC diagnostic push
224 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
225 - (void)application:(UIApplication*)application
226  didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
227  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
228  if (!delegate) {
229  continue;
230  }
231  if ([delegate respondsToSelector:_cmd]) {
232  [delegate application:application didRegisterUserNotificationSettings:notificationSettings];
233  }
234  }
235 }
236 #pragma GCC diagnostic pop
237 
238 - (void)application:(UIApplication*)application
239  didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
240  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
241  if (!delegate) {
242  continue;
243  }
244  if ([delegate respondsToSelector:_cmd]) {
245  [delegate application:application
246  didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
247  }
248  }
249 }
250 
251 - (void)application:(UIApplication*)application
252  didReceiveRemoteNotification:(NSDictionary*)userInfo
253  fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
254  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
255  if (!delegate) {
256  continue;
257  }
258  if ([delegate respondsToSelector:_cmd]) {
259  if ([delegate application:application
260  didReceiveRemoteNotification:userInfo
261  fetchCompletionHandler:completionHandler]) {
262  return;
263  }
264  }
265  }
266 }
267 
268 #pragma GCC diagnostic push
269 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
270 - (void)application:(UIApplication*)application
271  didReceiveLocalNotification:(UILocalNotification*)notification {
272  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
273  if (!delegate) {
274  continue;
275  }
276  if ([delegate respondsToSelector:_cmd]) {
277  [delegate application:application didReceiveLocalNotification:notification];
278  }
279  }
280 }
281 #pragma GCC diagnostic pop
282 
283 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
284  willPresentNotification:(UNNotification*)notification
285  withCompletionHandler:
286  (void (^)(UNNotificationPresentationOptions options))completionHandler
287  NS_AVAILABLE_IOS(10_0) {
288  if (@available(iOS 10.0, *)) {
289  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
290  if ([delegate respondsToSelector:_cmd]) {
291  [delegate userNotificationCenter:center
292  willPresentNotification:notification
293  withCompletionHandler:completionHandler];
294  }
295  }
296  }
297 }
298 
299 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
300  didReceiveNotificationResponse:(UNNotificationResponse*)response
301  withCompletionHandler:(void (^)(void))completionHandler NS_AVAILABLE_IOS(10_0) {
302  if (@available(iOS 10.0, *)) {
303  for (id<FlutterApplicationLifeCycleDelegate> delegate in _delegates) {
304  if ([delegate respondsToSelector:_cmd]) {
305  [delegate userNotificationCenter:center
306  didReceiveNotificationResponse:response
307  withCompletionHandler:completionHandler];
308  }
309  }
310  }
311 }
312 
313 - (BOOL)application:(UIApplication*)application
314  openURL:(NSURL*)url
315  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
316  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
317  if (!delegate) {
318  continue;
319  }
320  if ([delegate respondsToSelector:_cmd]) {
321  if ([delegate application:application openURL:url options:options]) {
322  return YES;
323  }
324  }
325  }
326  return NO;
327 }
328 
329 - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
330  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
331  if (!delegate) {
332  continue;
333  }
334  if ([delegate respondsToSelector:_cmd]) {
335  if ([delegate application:application handleOpenURL:url]) {
336  return YES;
337  }
338  }
339  }
340  return NO;
341 }
342 
343 - (BOOL)application:(UIApplication*)application
344  openURL:(NSURL*)url
345  sourceApplication:(NSString*)sourceApplication
346  annotation:(id)annotation {
347  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
348  if (!delegate) {
349  continue;
350  }
351  if ([delegate respondsToSelector:_cmd]) {
352  if ([delegate application:application
353  openURL:url
354  sourceApplication:sourceApplication
355  annotation:annotation]) {
356  return YES;
357  }
358  }
359  }
360  return NO;
361 }
362 
363 - (void)application:(UIApplication*)application
364  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
365  completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
366  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
367  if (!delegate) {
368  continue;
369  }
370  if ([delegate respondsToSelector:_cmd]) {
371  if ([delegate application:application
372  performActionForShortcutItem:shortcutItem
373  completionHandler:completionHandler]) {
374  return;
375  }
376  }
377  }
378 }
379 
380 - (BOOL)application:(UIApplication*)application
381  handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
382  completionHandler:(nonnull void (^)())completionHandler {
383  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
384  if (!delegate) {
385  continue;
386  }
387  if ([delegate respondsToSelector:_cmd]) {
388  if ([delegate application:application
389  handleEventsForBackgroundURLSession:identifier
390  completionHandler:completionHandler]) {
391  return YES;
392  }
393  }
394  }
395  return NO;
396 }
397 
398 - (BOOL)application:(UIApplication*)application
399  performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
400  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
401  if (!delegate) {
402  continue;
403  }
404  if ([delegate respondsToSelector:_cmd]) {
405  if ([delegate application:application performFetchWithCompletionHandler:completionHandler]) {
406  return YES;
407  }
408  }
409  }
410  return NO;
411 }
412 
413 - (BOOL)application:(UIApplication*)application
414  continueUserActivity:(NSUserActivity*)userActivity
415  restorationHandler:(void (^)(NSArray*))restorationHandler {
416  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
417  if (!delegate) {
418  continue;
419  }
420  if ([delegate respondsToSelector:_cmd]) {
421  if ([delegate application:application
422  continueUserActivity:userActivity
423  restorationHandler:restorationHandler]) {
424  return YES;
425  }
426  }
427  }
428  return NO;
429 }
430 @end
void setCachePath:(NSString *path)
NSPointerArray * _delegates
static const char * kCallbackCacheSubDir
static const SEL selectorsHandledByPlugins[]
std::string JoinPaths(std::initializer_list< std::string > components)
Definition: paths.cc:14
UIBackgroundTaskIdentifier _debugBackgroundTask