Skip to content

Commit 9d835f3

Browse files
committed
[macOS] Fix engine/binaryMessenger retain cycle
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 flutter#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. Issue: flutter/flutter#116445
1 parent ad109d3 commit 9d835f3

File tree

3 files changed

+42
-6
lines changed

3 files changed

+42
-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: 35 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,37 @@ - (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+
TEST_F(FlutterEngineTest, FlutterBinaryMessengerNullsParentOnEngineRelease) {
507+
FlutterBinaryMessengerRelay* relay = nil;
508+
@autoreleasepool {
509+
// Create a test engine.
510+
NSString* fixtures = @(flutter::testing::GetFixturesPath());
511+
FlutterDartProject* project = [[FlutterDartProject alloc]
512+
initWithAssetsPath:fixtures
513+
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
514+
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
515+
project:project
516+
allowHeadlessExecution:true];
517+
518+
// Get the binary messenger for the engine.
519+
id<FlutterBinaryMessenger> binaryMessenger = engine.binaryMessenger;
520+
ASSERT_TRUE([binaryMessenger isKindOfClass:[FlutterBinaryMessengerRelay class]]);
521+
relay = (FlutterBinaryMessengerRelay*)binaryMessenger;
522+
523+
// Verify the relay parent (the engine) is non-nil.
524+
EXPECT_NE(relay.parent, nil);
525+
}
526+
527+
// Once the engine has been deallocated, verify the relay parent is nil, and thus the engine is
528+
// not retained by the holder of the relay.
529+
EXPECT_EQ(relay.parent, nil);
530+
}
531+
499532
// If a channel overrides a previous channel with the same name, cleaning
500533
// the previous channel should not affect the new channel.
501534
//

0 commit comments

Comments
 (0)