Skip to content

Commit

Permalink
Allow FlutterViewController to be released when not initialized with …
Browse files Browse the repository at this point in the history
…an engine (flutter#6879)

* Break cycle between FlutterViewController and FlutterEngine
  • Loading branch information
dnfield committed Jan 15, 2019
1 parent 9d206e2 commit 9004af1
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 14 deletions.
3 changes: 3 additions & 0 deletions shell/platform/darwin/ios/framework/Headers/Flutter.h
Expand Up @@ -8,6 +8,9 @@
/**
BREAKING CHANGES:
December 17, 2018:
- Changed designated initializer on FlutterEngine
October 5, 2018:
- Removed FlutterNavigationController.h/.mm
- Changed return signature of `FlutterDartHeadlessCodeRunner.run*` from void
Expand Down
22 changes: 21 additions & 1 deletion shell/platform/darwin/ios/framework/Headers/FlutterEngine.h
Expand Up @@ -55,8 +55,28 @@ FLUTTER_EXPORT
* the threads used by this FlutterEngine.
* @param projectOrNil The `FlutterDartProject` to run.
*/
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)projectOrNil;

/**
* Initialize this FlutterEngine with a `FlutterDartProject`.
*
* If the FlutterDartProject is not specified, the FlutterEngine will attempt to locate
* the project in a default location (the flutter_assets folder in the iOS application
* bundle).
*
* A newly initialized engine will not run the `FlutterDartProject` until either
* `-runWithEntrypoint:` or `-runWithEntrypoint:libraryURI:` is called.
*
* @param labelPrefix The label prefix used to identify threads for this instance. Should
* be unique across FlutterEngine instances, and is used in instrumentation to label
* the threads used by this FlutterEngine.
* @param projectOrNil The `FlutterDartProject` to run.
* @param allowHeadlessExecution Whether or not to allow this instance to continue
* running after passing a nil `FlutterViewController` to `-setViewController:`.
*/
- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)projectOrNil NS_DESIGNATED_INITIALIZER;
project:(FlutterDartProject*)projectOrNil
allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER;

/**
* The default initializer is not available for this object.
Expand Down
Expand Up @@ -46,8 +46,25 @@ FLUTTER_DEPRECATED("FlutterEngine should be used rather than FlutterHeadlessDart
* be unique across FlutterEngine instances
* @param projectOrNil The `FlutterDartProject` to run.
*/
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)projectOrNil;

/**
* Iniitalize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
*
* If the FlutterDartProject is not specified, the FlutterHeadlessDartRunner will attempt to locate
* the project in a default location.
*
* A newly initialized engine will not run the `FlutterDartProject` until either
* `-runWithEntrypoint:` or `-runWithEntrypoint:libraryURI` is called.
*
* @param labelPrefix The label prefix used to identify threads for this instance. Should
* be unique across FlutterEngine instances
* @param projectOrNil The `FlutterDartProject` to run.
* @param allowHeadlessExecution Must be set to `YES`.
*/
- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)projectOrNil NS_DESIGNATED_INITIALIZER;
project:(FlutterDartProject*)projectOrNil
allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER;

/**
* Not recommended for use - will initialize with a default label ("io.flutter.headless")
Expand Down
Expand Up @@ -151,6 +151,11 @@ FLUTTER_EXPORT
*/
@property(nonatomic, getter=isViewOpaque) BOOL viewOpaque;

/**
* The `FlutterEngine` instance for this view controller.
*/
@property(weak, nonatomic, readonly) FlutterEngine* engine;

@end

#endif // FLUTTER_FLUTTERVIEWCONTROLLER_H_
41 changes: 37 additions & 4 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Expand Up @@ -60,12 +60,22 @@ @implementation FlutterEngine {
fml::scoped_nsobject<FlutterBasicMessageChannel> _settingsChannel;

int64_t _nextTextureId;

BOOL _allowHeadlessExecution;
}

- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)projectOrNil {
return [self initWithName:labelPrefix project:projectOrNil allowHeadlessExecution:YES];
}

- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)projectOrNil
allowHeadlessExecution:(BOOL)allowHeadlessExecution {
self = [super init];
NSAssert(self, @"Super init cannot be nil");
NSAssert(labelPrefix, @"labelPrefix is required");

_allowHeadlessExecution = allowHeadlessExecution;
_labelPrefix = [labelPrefix copy];

_weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(self);
Expand All @@ -76,7 +86,6 @@ - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*
_dartProject.reset([projectOrNil retain]);

_pluginPublications = [NSMutableDictionary new];
_publisher.reset([[FlutterObservatoryPublisher alloc] init]);
_platformViewsController.reset(new shell::FlutterPlatformViewsController());

[self setupChannels];
Expand Down Expand Up @@ -135,7 +144,14 @@ - (void)setViewController:(FlutterViewController*)viewController {
FML_DCHECK(self.iosPlatformView);
_viewController = [viewController getWeakPtr];
self.iosPlatformView->SetOwnerViewController(_viewController);
[self maybeSetupPlatformViewChannels];
if (!viewController && !_allowHeadlessExecution) {
[self resetChannels];

_shell.reset();
_threadHost.Reset();
} else {
[self maybeSetupPlatformViewChannels];
}
}

- (FlutterViewController*)viewController {
Expand Down Expand Up @@ -176,6 +192,20 @@ - (FlutterBasicMessageChannel*)settingsChannel {
return _settingsChannel.get();
}

- (void)resetChannels {
_localizationChannel.reset();
_navigationChannel.reset();
_platformChannel.reset();
_platformViewsChannel.reset();
_textInputChannel.reset();
_lifecycleChannel.reset();
_systemChannel.reset();
_settingsChannel.reset();
}

// If you add a channel, be sure to also update `resetChannels`.
// Channels get a reference to the engine, and therefore need manual
// cleanup for proper collection.
- (void)setupChannels {
_localizationChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/localization"
Expand Down Expand Up @@ -221,8 +251,6 @@ - (void)setupChannels {
_textInputPlugin.get().textInputDelegate = self;

_platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]);

[self maybeSetupPlatformViewChannels];
}

- (void)maybeSetupPlatformViewChannels {
Expand Down Expand Up @@ -348,6 +376,11 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: "
<< entrypoint.UTF8String;
} else {
[self setupChannels];
if (!_platformViewsController) {
_platformViewsController.reset(new shell::FlutterPlatformViewsController());
}
_publisher.reset([[FlutterObservatoryPublisher alloc] init]);
[self maybeSetupPlatformViewChannels];
}

Expand Down
Expand Up @@ -28,7 +28,17 @@ @implementation FlutterHeadlessDartRunner {
}

- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)projectOrNil {
return [super initWithName:labelPrefix project:projectOrNil];
return [self initWithName:labelPrefix project:projectOrNil allowHeadlessExecution:YES];
}

- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)projectOrNil
allowHeadlessExecution:(BOOL)allowHeadlessExecution {
NSAssert(allowHeadlessExecution == YES,
@"Cannot initialize a FlutterHeadlessDartRunner without headless execution.");
return [super initWithName:labelPrefix
project:projectOrNil
allowHeadlessExecution:allowHeadlessExecution];
}
- (instancetype)init {
return [self initWithName:@"io.flutter.headless" project:nil];
Expand Down
Expand Up @@ -67,7 +67,9 @@ - (instancetype)initWithProject:(FlutterDartProject*)projectOrNil
if (self) {
_viewOpaque = YES;
_weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
_engine.reset([[FlutterEngine alloc] initWithName:@"io.flutter" project:projectOrNil]);
_engine.reset([[FlutterEngine alloc] initWithName:@"io.flutter"
project:projectOrNil
allowHeadlessExecution:NO]);
_flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
[_engine.get() createShell:nil libraryURI:nil];
_engineNeedsLaunch = YES;
Expand Down Expand Up @@ -116,8 +118,8 @@ - (void)performCommonViewControllerInitialization {
[self setupNotificationCenterObservers];
}

- (fml::scoped_nsobject<FlutterEngine>)engine {
return _engine;
- (FlutterEngine*)engine {
return _engine.get();
}

- (fml::WeakPtr<FlutterViewController>)getWeakPtr {
Expand Down Expand Up @@ -389,9 +391,9 @@ - (void)viewWillAppear:(BOOL)animated {

if (_engineNeedsLaunch) {
[_engine.get() launchEngine:nil libraryURI:nil];
[_engine.get() setViewController:self];
_engineNeedsLaunch = NO;
}
[_engine.get() setViewController:self];

// Only recreate surface on subsequent appearances when viewport metrics are known.
// First time surface creation is done on viewDidLayoutSubviews.
Expand Down Expand Up @@ -423,7 +425,7 @@ - (void)viewDidDisappear:(BOOL)animated {
TRACE_EVENT0("flutter", "viewDidDisappear");
[self surfaceUpdated:NO];
[[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.paused"];

[_engine.get() setViewController:nil];
[super viewDidDisappear:animated];
}

Expand Down
Expand Up @@ -17,8 +17,6 @@
- (fml::WeakPtr<FlutterViewController>)getWeakPtr;
- (shell::FlutterPlatformViewsController*)platformViewsController;

@property(readonly) fml::scoped_nsobject<FlutterEngine> engine;

@end

#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWCONTROLLER_INTERNAL_H_

0 comments on commit 9004af1

Please sign in to comment.