Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 22bd35a

Browse files
authored
[macOS] Fix engine/binaryMessenger retain cycle (#44471)
Previously, FlutterEngine.binaryMessenger returned the FlutterEngine instance itself, which meant that channels/plugins could hold a strong reference to the engine and thus cause a retain cycle, preventing the engine from being deallocated. We introduce FlutterBinaryMessengerRelay, which implements the FlutterBinaryMessenger protocol, by delegating back to the engine, to which it holds a weak reference, thus avoiding the retain cycle. This also changes the FlutterBinaryMessengerRelay.parent property from an assign property to a weak property since that code is compiled with ARC enabled as of #44395. This patch also rearranges the headers to comply with our style guide: related headers for this file, C headers, C++ headers, other Flutter headers, in that order. Fixes: flutter/flutter#116445
1 parent 6987702 commit 22bd35a

File tree

3 files changed

+43
-6
lines changed

3 files changed

+43
-6
lines changed

shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
FLUTTER_DARWIN_EXPORT
1313
#endif
1414
@interface FlutterBinaryMessengerRelay : NSObject <FlutterBinaryMessenger>
15-
@property(nonatomic, assign) NSObject<FlutterBinaryMessenger>* parent;
15+
@property(nonatomic, weak) NSObject<FlutterBinaryMessenger>* parent;
1616
- (instancetype)initWithParent:(NSObject<FlutterBinaryMessenger>*)parent;
1717
@end
1818

shell/platform/darwin/macos/framework/Source/FlutterEngine.mm

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "flutter/shell/platform/common/engine_switches.h"
1414
#include "flutter/shell/platform/embedder/embedder.h"
1515

16+
#import "flutter/shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h"
1617
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
1718
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
1819
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
@@ -402,6 +403,9 @@ @implementation FlutterEngine {
402403

403404
// Whether any portion of the application is currently visible.
404405
BOOL _visible;
406+
407+
// Proxy to allow plugins, channels to hold a weak reference to the binary messenger (self).
408+
FlutterBinaryMessengerRelay* _binaryMessenger;
405409
}
406410

407411
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
@@ -420,6 +424,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix
420424
_currentMessengerConnection = 1;
421425
_allowHeadlessExecution = allowHeadlessExecution;
422426
_semanticsEnabled = NO;
427+
_binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
423428
_isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
424429
[_isResponseValid addObject:@YES];
425430
// kFlutterImplicitViewId is reserved for the implicit view.
@@ -723,9 +728,7 @@ - (FlutterCompositor*)createFlutterCompositor {
723728
}
724729

725730
- (id<FlutterBinaryMessenger>)binaryMessenger {
726-
// TODO(stuartmorgan): Switch to FlutterBinaryMessengerRelay to avoid plugins
727-
// keeping the engine alive.
728-
return self;
731+
return _binaryMessenger;
729732
}
730733

731734
#pragma mark - Framework-internal methods

shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#include <objc/objc.h>
65
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
76
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
8-
#include "gtest/gtest.h"
7+
8+
#include <objc/objc.h>
99

1010
#include <functional>
1111
#include <thread>
@@ -14,13 +14,15 @@
1414
#include "flutter/lib/ui/window/platform_message.h"
1515
#include "flutter/shell/platform/common/accessibility_bridge.h"
1616
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
17+
#import "flutter/shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h"
1718
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
1819
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h"
1920
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h"
2021
#include "flutter/shell/platform/embedder/embedder.h"
2122
#include "flutter/shell/platform/embedder/embedder_engine.h"
2223
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
2324
#include "flutter/testing/test_dart_native_resolver.h"
25+
#include "gtest/gtest.h"
2426

2527
// CREATE_NATIVE_ENTRY and MOCK_ENGINE_PROC are leaky by design
2628
// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
@@ -496,6 +498,38 @@ - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication* _Nonnu
496498
EXPECT_TRUE(called);
497499
}
498500

501+
// Verify that the engine is not retained indirectly via the binary messenger held by channels and
502+
// plugins. Previously, FlutterEngine.binaryMessenger returned the engine itself, and thus plugins
503+
// could cause a retain cycle, preventing the engine from being deallocated.
504+
// FlutterEngine.binaryMessenger now returns a FlutterBinaryMessengerRelay whose pointer back to
505+
// the engine is cleared when the engine is deallocated.
506+
// Issue: https://github.com/flutter/flutter/issues/116445
507+
TEST_F(FlutterEngineTest, FlutterBinaryMessengerNullsParentOnEngineRelease) {
508+
FlutterBinaryMessengerRelay* relay = nil;
509+
@autoreleasepool {
510+
// Create a test engine.
511+
NSString* fixtures = @(flutter::testing::GetFixturesPath());
512+
FlutterDartProject* project = [[FlutterDartProject alloc]
513+
initWithAssetsPath:fixtures
514+
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
515+
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
516+
project:project
517+
allowHeadlessExecution:true];
518+
519+
// Get the binary messenger for the engine.
520+
id<FlutterBinaryMessenger> binaryMessenger = engine.binaryMessenger;
521+
ASSERT_TRUE([binaryMessenger isKindOfClass:[FlutterBinaryMessengerRelay class]]);
522+
relay = (FlutterBinaryMessengerRelay*)binaryMessenger;
523+
524+
// Verify the relay parent (the engine) is non-nil.
525+
EXPECT_NE(relay.parent, nil);
526+
}
527+
528+
// Once the engine has been deallocated, verify the relay parent is nil, and thus the engine is
529+
// not retained by the holder of the relay.
530+
EXPECT_EQ(relay.parent, nil);
531+
}
532+
499533
// If a channel overrides a previous channel with the same name, cleaning
500534
// the previous channel should not affect the new channel.
501535
//

0 commit comments

Comments
 (0)