iOS: Reduce engine/view controller coupling (flutter/engine#57151)

Eliminates some cases where `FlutterViewController` was relying on `FlutterEngine` internals:
* `[FlutterEngine shell]`
* `[FlutterEngine platformView]`
* `[FlutterEngine iosPlatformView]`

Instead, `FlutterEngine` now exposes:
* `installFirstFrameCallback:`
* `enableSemantics:withFlags:`
* `notifyViewCreated`
* `notifyViewDestroyed`
* `waitForFirstFrameSync:callback:`

Also fixes a couple cases where we were relying on transitive header includes:
* `FlutterAppController` relied on `FlutterViewController_Internal.h` for `sendDeepLinkToFramework:completionHandler:`

This is a refactoring followup to https://github.com/flutter/engine/pull/57099 that introduces no semantic changes.

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
Chris Bracken 2024-12-12 10:38:07 -08:00 committed by GitHub
parent 5f3164ad3d
commit e9965b2d07
10 changed files with 142 additions and 119 deletions

View File

@ -10,6 +10,7 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
FLUTTER_ASSERT_ARC

View File

@ -319,14 +319,48 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
self.platformView->DispatchPointerDataPacket(std::move(packet));
}
- (fml::WeakPtr<flutter::PlatformView>)platformView {
if (!_shell) {
return {};
- (void)installFirstFrameCallback:(void (^)(void))block {
if (!self.platformView) {
return;
}
return _shell->GetPlatformView();
__weak FlutterEngine* weakSelf = self;
self.platformView->SetNextFrameCallback([weakSelf, block] {
FlutterEngine* strongSelf = weakSelf;
if (!strongSelf) {
return;
}
FML_DCHECK(strongSelf.platformTaskRunner);
FML_DCHECK(strongSelf.rasterTaskRunner);
FML_DCHECK(strongSelf.rasterTaskRunner->RunsTasksOnCurrentThread());
// Get callback on raster thread and jump back to platform thread.
strongSelf.platformTaskRunner->PostTask([block]() { block(); });
});
}
- (flutter::PlatformViewIOS*)iosPlatformView {
- (void)enableSemantics:(BOOL)enabled withFlags:(int64_t)flags {
if (!self.platformView) {
return;
}
self.platformView->SetSemanticsEnabled(enabled);
self.platformView->SetAccessibilityFeatures(flags);
}
- (void)notifyViewCreated {
if (!self.platformView) {
return;
}
self.platformView->NotifyCreated();
}
- (void)notifyViewDestroyed {
if (!self.platformView) {
return;
}
self.platformView->NotifyDestroyed();
}
- (flutter::PlatformViewIOS*)platformView {
if (!_shell) {
return nullptr;
}
@ -402,16 +436,16 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
}
- (void)ensureSemanticsEnabled {
if (!self.iosPlatformView) {
if (!self.platformView) {
return;
}
self.iosPlatformView->SetSemanticsEnabled(true);
self.platformView->SetSemanticsEnabled(true);
}
- (void)setViewController:(FlutterViewController*)viewController {
FML_DCHECK(self.iosPlatformView);
FML_DCHECK(self.platformView);
_viewController = viewController;
self.iosPlatformView->SetOwnerViewController(_viewController);
self.platformView->SetOwnerViewController(_viewController);
[self maybeSetupPlatformViewChannels];
[self updateDisplays];
self.textInputPlugin.viewController = viewController;
@ -432,8 +466,8 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
}
- (void)attachView {
FML_DCHECK(self.iosPlatformView);
self.iosPlatformView->attachView();
FML_DCHECK(self.platformView);
self.platformView->attachView();
}
- (void)setFlutterViewControllerWillDeallocObserver:(id<NSObject>)observer {
@ -451,11 +485,8 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
self.textInputPlugin.viewController = nil;
if (!self.allowHeadlessExecution) {
[self destroyContext];
} else if (_shell) {
flutter::PlatformViewIOS* platform_view = [self iosPlatformView];
if (platform_view) {
platform_view->SetOwnerViewController({});
}
} else if (self.platformView) {
self.platformView->SetOwnerViewController({});
}
[self.textInputPlugin resetViewResponder];
_viewController = nil;
@ -1215,8 +1246,8 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
NSParameterAssert(channel);
if (_shell && _shell->IsSetup()) {
self.iosPlatformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String,
handler, taskQueue);
self.platformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String,
handler, taskQueue);
return _connections->AquireConnection(channel.UTF8String);
} else {
NSAssert(!handler, @"Setting a message handler before the FlutterEngine has been run.");
@ -1229,8 +1260,8 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
if (_shell && _shell->IsSetup()) {
std::string channel = _connections->CleanupConnection(connection);
if (!channel.empty()) {
self.iosPlatformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.c_str(), nil,
nil);
self.platformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.c_str(), nil,
nil);
}
}
}
@ -1238,9 +1269,9 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
#pragma mark - FlutterTextureRegistry
- (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture {
FML_DCHECK(self.iosPlatformView);
FML_DCHECK(self.platformView);
int64_t textureId = self.nextTextureId++;
self.iosPlatformView->RegisterExternalTexture(textureId, texture);
self.platformView->RegisterExternalTexture(textureId, texture);
return textureId;
}
@ -1350,6 +1381,13 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
[self.localizationChannel invokeMethod:@"setLocale" arguments:localeData];
}
- (void)waitForFirstFrameSync:(NSTimeInterval)timeout
callback:(NS_NOESCAPE void (^_Nonnull)(BOOL didTimeout))callback {
fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000);
fml::Status status = self.shell.WaitForFirstFrame(waitTime);
callback(status.code() == fml::StatusCode::kDeadlineExceeded);
}
- (void)waitForFirstFrame:(NSTimeInterval)timeout
callback:(void (^_Nonnull)(BOOL didTimeout))callback {
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
@ -1364,8 +1402,8 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
}
fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000);
didTimeout =
strongSelf.shell.WaitForFirstFrame(waitTime).code() == fml::StatusCode::kDeadlineExceeded;
fml::Status status = strongSelf.shell.WaitForFirstFrame(waitTime);
didTimeout = status.code() == fml::StatusCode::kDeadlineExceeded;
});
// Only execute the main queue task once the background task has completely finished executing.

View File

@ -11,6 +11,7 @@
#include "flutter/fml/message_loop.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
FLUTTER_ASSERT_ARC
@ -101,7 +102,7 @@ flutter::FakeDelegate fake_delegate;
XCTAssertNotNil(engine);
id mockEngine = OCMPartialMock(engine);
OCMStub([mockEngine notifyLowMemory]);
OCMStub([mockEngine iosPlatformView]).andReturn(platform_view.get());
OCMStub([mockEngine platformView]).andReturn(platform_view.get());
[engine setViewController:nil];
OCMVerify([mockEngine notifyLowMemory]);

View File

@ -75,8 +75,6 @@ FLUTTER_ASSERT_ARC
XCTAssertNotNil(engine);
// Ensure getters don't deref _shell when it's null, and instead return nullptr.
XCTAssertEqual(engine.platformView.get(), nullptr);
XCTAssertEqual(engine.iosPlatformView, nullptr);
XCTAssertEqual(engine.platformTaskRunner.get(), nullptr);
XCTAssertEqual(engine.uiTaskRunner.get(), nullptr);
XCTAssertEqual(engine.rasterTaskRunner.get(), nullptr);
@ -435,10 +433,10 @@ FLUTTER_ASSERT_ARC
initialRoute:nil
entrypointArgs:nil];
XCTAssertNotNil(spawn);
XCTAssertTrue([engine iosPlatformView] != nullptr);
XCTAssertTrue([spawn iosPlatformView] != nullptr);
std::shared_ptr<flutter::IOSContext> engine_context = [engine iosPlatformView]->GetIosContext();
std::shared_ptr<flutter::IOSContext> spawn_context = [spawn iosPlatformView]->GetIosContext();
XCTAssertTrue(engine.platformView != nullptr);
XCTAssertTrue(spawn.platformView != nullptr);
std::shared_ptr<flutter::IOSContext> engine_context = engine.platformView->GetIosContext();
std::shared_ptr<flutter::IOSContext> spawn_context = spawn.platformView->GetIosContext();
XCTAssertEqual(engine_context, spawn_context);
}

View File

@ -25,14 +25,12 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
NS_ASSUME_NONNULL_BEGIN
@interface FlutterEngine () <FlutterViewEngineDelegate>
- (flutter::Shell&)shell;
- (void)updateViewportMetrics:(flutter::ViewportMetrics)viewportMetrics;
- (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet;
@ -40,7 +38,10 @@ NS_ASSUME_NONNULL_BEGIN
- (fml::RefPtr<fml::TaskRunner>)uiTaskRunner;
- (fml::RefPtr<fml::TaskRunner>)rasterTaskRunner;
- (fml::WeakPtr<flutter::PlatformView>)platformView;
- (void)installFirstFrameCallback:(void (^)(void))block;
- (void)enableSemantics:(BOOL)enabled withFlags:(int64_t)flags;
- (void)notifyViewCreated;
- (void)notifyViewDestroyed;
- (flutter::Rasterizer::Screenshot)screenshot:(flutter::Rasterizer::ScreenshotType)type
base64Encode:(bool)base64Encode;
@ -56,8 +57,13 @@ NS_ASSUME_NONNULL_BEGIN
initialRoute:(nullable NSString*)initialRoute;
- (void)attachView;
- (void)notifyLowMemory;
- (flutter::PlatformViewIOS*)iosPlatformView;
/// Blocks until the first frame is presented or the timeout is exceeded, then invokes callback.
- (void)waitForFirstFrameSync:(NSTimeInterval)timeout
callback:(NS_NOESCAPE void (^)(BOOL didTimeout))callback;
/// Asynchronously waits until the first frame is presented or the timeout is exceeded, then invokes
/// callback.
- (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback;
/**

View File

@ -8,6 +8,7 @@
#import "flutter/shell/common/shell.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"
#import "flutter/shell/platform/darwin/ios/rendering_api_selection.h"
#include "flutter/shell/platform/embedder/embedder.h"
@ -24,6 +25,8 @@ class ThreadHost;
@property(readonly, nonatomic) BOOL enableEmbedderAPI;
- (flutter::Shell&)shell;
- (flutter::PlatformViewIOS*)platformView;
- (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger;
- (flutter::IOSRenderingAPI)platformViewsRenderingAPI;
- (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback;

View File

@ -16,7 +16,7 @@
#include "flutter/fml/thread.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h"
@ -4319,8 +4319,8 @@ fml::RefPtr<fml::TaskRunner> GetDefaultTaskRunner() {
// Create an IOSContext and GrDirectContext.
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
[engine run];
XCTAssertTrue([engine iosPlatformView] != nullptr);
auto ios_context = [engine iosPlatformView]->GetIosContext();
XCTAssertTrue(engine.platformView != nullptr);
auto ios_context = engine.platformView->GetIosContext();
auto gr_context = ios_context->GetMainContext();
auto pool = flutter::OverlayLayerPool{};

View File

@ -642,23 +642,10 @@ static void SendFakeTouchEvent(UIScreen* screen,
if (!self.engine) {
return;
}
fml::WeakPtr<flutter::PlatformViewIOS> weakPlatformView = self.engine.platformView;
if (!weakPlatformView) {
return;
}
// Start on the platform thread.
__weak FlutterViewController* weakSelf = self;
weakPlatformView->SetNextFrameCallback([weakSelf,
platformTaskRunner = self.engine.platformTaskRunner,
rasterTaskRunner = self.engine.rasterTaskRunner]() {
FML_DCHECK(platformTaskRunner);
FML_DCHECK(rasterTaskRunner);
FML_DCHECK(rasterTaskRunner->RunsTasksOnCurrentThread());
// Get callback on raster thread and jump back to platform thread.
platformTaskRunner->PostTask([weakSelf]() { [weakSelf onFirstFrameRendered]; });
});
[self.engine installFirstFrameCallback:^{
[weakSelf onFirstFrameRendered];
}];
}
#pragma mark - Properties
@ -749,10 +736,10 @@ static void SendFakeTouchEvent(UIScreen* screen,
[self installFirstFrameCallback];
self.platformViewsController.flutterView = self.flutterView;
self.platformViewsController.flutterViewController = self;
[self.engine iosPlatformView]->NotifyCreated();
[self.engine notifyViewCreated];
} else {
self.displayingFlutterUI = NO;
[self.engine iosPlatformView]->NotifyDestroyed();
[self.engine notifyViewDestroyed];
self.platformViewsController.flutterView = nil;
self.platformViewsController.flutterViewController = nil;
}
@ -1333,13 +1320,11 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
return;
}
flutter::Shell& shell = self.engine.shell;
auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
// Do nothing in this block. Just trigger system to callback touch events with correct rate.
};
_touchRateCorrectionVSyncClient =
[[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetPlatformTaskRunner()
callback:callback];
[[VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
_touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
}
@ -1416,19 +1401,21 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
// the viewport metrics update tasks.
if (firstViewBoundsUpdate && applicationOrSceneIsActive && self.engine) {
[self surfaceUpdated:YES];
flutter::Shell& shell = self.engine.shell;
fml::TimeDelta waitTime =
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
fml::TimeDelta::FromMilliseconds(200);
NSTimeInterval timeout = 0.2;
#else
fml::TimeDelta::FromMilliseconds(100);
NSTimeInterval timeout = 0.1;
#endif
if (shell.WaitForFirstFrame(waitTime).code() == fml::StatusCode::kDeadlineExceeded) {
FML_LOG(INFO) << "Timeout waiting for the first frame to render. This may happen in "
<< "unoptimized builds. If this is a release build, you should load a less "
<< "complex frame to avoid the timeout.";
}
[self.engine
waitForFirstFrameSync:timeout
callback:^(BOOL didTimeout) {
if (didTimeout) {
FML_LOG(INFO)
<< "Timeout waiting for the first frame to render. This may happen in "
"unoptimized builds. If this is a release build, you should load a "
"less complex frame to avoid the timeout.";
}
}];
}
}
@ -2106,26 +2093,22 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
if (!self.engine) {
return;
}
fml::WeakPtr<flutter::PlatformView> platformView = self.engine.platformView;
if (!platformView) {
return;
}
BOOL enabled = NO;
int32_t flags = self.accessibilityFlags;
#if TARGET_OS_SIMULATOR
// There doesn't appear to be any way to determine whether the accessibility
// inspector is enabled on the simulator. We conservatively always turn on the
// accessibility bridge in the simulator, but never assistive technology.
platformView->SetSemanticsEnabled(true);
platformView->SetAccessibilityFeatures(flags);
enabled = YES;
#else
_isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
bool enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
if (enabled) {
flags |= static_cast<int32_t>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
}
platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled());
platformView->SetAccessibilityFeatures(flags);
enabled |= UIAccessibilityIsSpeakScreenEnabled();
#endif
[self.engine enableSemantics:enabled withFlags:flags];
}
- (int32_t)accessibilityFlags {

View File

@ -14,6 +14,7 @@
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEmbedderKeyResponder.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
@ -27,14 +28,6 @@ FLUTTER_ASSERT_ARC
using namespace flutter::testing;
@interface FlutterEngine ()
- (FlutterTextInputPlugin*)textInputPlugin;
- (void)sendKeyEvent:(const FlutterKeyEvent&)event
callback:(nullable FlutterKeyEventCallback)callback
userData:(nullable void*)userData;
- (fml::RefPtr<fml::TaskRunner>)uiTaskRunner;
@end
/// Sometimes we have to use a custom mock to avoid retain cycles in OCMock.
/// Used for testing low memory notification.
@interface FlutterEnginePartialMock : FlutterEngine

View File

@ -87,6 +87,38 @@ class PlatformViewIOS final : public PlatformView {
// |PlatformView|
void SetSemanticsEnabled(bool enabled) override;
// |PlatformView|
void HandlePlatformMessage(std::unique_ptr<flutter::PlatformMessage> message) override;
// |PlatformView|
std::unique_ptr<Surface> CreateRenderingSurface() override;
// |PlatformView|
std::shared_ptr<ExternalViewEmbedder> CreateExternalViewEmbedder() override;
// |PlatformView|
sk_sp<GrDirectContext> CreateResourceContext() const override;
// |PlatformView|
std::shared_ptr<impeller::Context> GetImpellerContext() const override;
// |PlatformView|
void SetAccessibilityFeatures(int32_t flags) override;
// |PlatformView|
void UpdateSemantics(flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) override;
// |PlatformView|
std::unique_ptr<VsyncWaiter> CreateVSyncWaiter() override;
// |PlatformView|
void OnPreEngineRestart() const override;
// |PlatformView|
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocales(
const std::vector<std::string>& supported_locale_data) override;
/** Accessor for the `IOSContext` associated with the platform view. */
const std::shared_ptr<IOSContext>& GetIosContext() { return ios_context_; }
@ -143,38 +175,6 @@ class PlatformViewIOS final : public PlatformView {
std::vector<std::string> platform_resolved_locale_;
std::shared_ptr<PlatformMessageHandlerIos> platform_message_handler_;
// |PlatformView|
void HandlePlatformMessage(std::unique_ptr<flutter::PlatformMessage> message) override;
// |PlatformView|
std::unique_ptr<Surface> CreateRenderingSurface() override;
// |PlatformView|
std::shared_ptr<ExternalViewEmbedder> CreateExternalViewEmbedder() override;
// |PlatformView|
sk_sp<GrDirectContext> CreateResourceContext() const override;
// |PlatformView|
std::shared_ptr<impeller::Context> GetImpellerContext() const override;
// |PlatformView|
void SetAccessibilityFeatures(int32_t flags) override;
// |PlatformView|
void UpdateSemantics(flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) override;
// |PlatformView|
std::unique_ptr<VsyncWaiter> CreateVSyncWaiter() override;
// |PlatformView|
void OnPreEngineRestart() const override;
// |PlatformView|
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocales(
const std::vector<std::string>& supported_locale_data) override;
FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewIOS);
};