Skip to content

Commit

Permalink
Dynamically add certain iOS AppDelegate methods. (flutter#8843)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Yang authored May 30, 2019
1 parent 58eff77 commit 49b6de8
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 14 deletions.
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatfor
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
*/
FLUTTER_EXPORT
@interface FlutterPluginAppLifeCycleDelegate : NSObject

/**
* Registers `delegate` to receive life cycle callbacks via this FlutterPluginAppLifecycleDelegate
* as long as it is alive.
Expand Down
71 changes: 57 additions & 14 deletions shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
// found in the LICENSE file.

#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h"
#include "flutter/fml/logging.h"
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"

static NSString* kUIBackgroundMode = @"UIBackgroundModes";
static NSString* kRemoteNotificationCapabitiliy = @"remote-notification";
static NSString* kBackgroundFetchCapatibility = @"fetch";

@implementation FlutterAppDelegate {
FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate;
Expand Down Expand Up @@ -86,14 +92,6 @@ - (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[_lifeCycleDelegate application:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
didReceiveLocalNotification:(UILocalNotification*)notification {
[_lifeCycleDelegate application:application didReceiveLocalNotification:notification];
Expand Down Expand Up @@ -147,11 +145,6 @@ - (void)application:(UIApplication*)application
completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
- (BOOL)application:(UIApplication*)application
continueUserActivity:(NSUserActivity*)userActivity
Expand Down Expand Up @@ -195,10 +188,60 @@ - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
return nil;
}

#pragma mark - FlutterAppLifeCycleProvider methods
#pragma mark - Selectors handling

- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
[_lifeCycleDelegate addDelegate:delegate];
}

#pragma mark - UIApplicationDelegate method dynamic implementation

- (BOOL)respondsToSelector:(SEL)selector {
if ([_lifeCycleDelegate isSelectorAddedDynamically:selector]) {
return [self delegateRespondsSelectorToPlugins:selector];
}
return [super respondsToSelector:selector];
}

- (BOOL)delegateRespondsSelectorToPlugins:(SEL)selector {
if ([_lifeCycleDelegate hasPluginThatRespondsToSelector:selector]) {
return [_lifeCycleDelegate respondsToSelector:selector];
} else {
return NO;
}
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([_lifeCycleDelegate isSelectorAddedDynamically:aSelector]) {
[self logCapabilityConfigurationWarningIfNeeded:aSelector];
return _lifeCycleDelegate;
}
return [super forwardingTargetForSelector:aSelector];
}

// Mimic the logging from Apple when the capability is not set for the selectors.
// However the difference is that Apple logs these message when the app launches, we only
// log it when the method is invoked. We can possibly also log it when the app launches, but
// it will cause an additional scan over all the plugins.
- (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector {
NSArray* backgroundModesArray =
[[NSBundle mainBundle] objectForInfoDictionaryKey:kUIBackgroundMode];
NSSet* backgroundModesSet = [[[NSSet alloc] initWithArray:backgroundModesArray] autorelease];
if (selector == @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)) {
if (![backgroundModesSet containsObject:kRemoteNotificationCapabitiliy]) {
NSLog(
@"You've implemented -[<UIApplicationDelegate> "
@"application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need "
@"to add \"remote-notification\" to the list of your supported UIBackgroundModes in your "
@"Info.plist.");
}
} else if (selector == @selector(application:performFetchWithCompletionHandler:)) {
if (![backgroundModesSet containsObject:kBackgroundFetchCapatibility]) {
NSLog(@"You've implemented -[<UIApplicationDelegate> "
@"application:performFetchWithCompletionHandler:], but you still need to add \"fetch\" "
@"to the list of your supported UIBackgroundModes in your Info.plist.");
}
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

static const char* kCallbackCacheSubDir = "Library/Caches/";

static const SEL selectorsHandledByPlugins[] = {
@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:),
@selector(application:performFetchWithCompletionHandler:)};

@implementation FlutterPluginAppLifeCycleDelegate {
UIBackgroundTaskIdentifier _debugBackgroundTask;

Expand All @@ -36,6 +40,27 @@ static BOOL isPowerOfTwo(NSUInteger x) {
return x != 0 && (x & (x - 1)) == 0;
}

- (BOOL)isSelectorAddedDynamically:(SEL)selector {
for (const SEL& aSelector : selectorsHandledByPlugins) {
if (selector == aSelector) {
return YES;
}
}
return NO;
}

- (BOOL)hasPluginThatRespondsToSelector:(SEL)selector {
for (id<FlutterPlugin> plugin in [_pluginDelegates allObjects]) {
if (!plugin) {
continue;
}
if ([plugin respondsToSelector:selector]) {
return YES;
}
}
return NO;
}

- (void)addDelegate:(NSObject<FlutterPlugin>*)delegate {
[_pluginDelegates addPointer:(__bridge void*)delegate];
if (isPowerOfTwo([_pluginDelegates count])) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@interface FlutterPluginAppLifeCycleDelegate ()

/**
* Check whether the selector should be handled dynamically.
*/
- (BOOL)isSelectorAddedDynamically:(SEL)selector;

/**
* Check whether there is at least one plugin responds to the selector.
*/
- (BOOL)hasPluginThatRespondsToSelector:(SEL)selector;

@end
;

0 comments on commit 49b6de8

Please sign in to comment.