iOS: Eliminate FlutterEngine dealloc notification (flutter/engine#56650)

FlutterEngineGroup keeps an array of all live FlutterEngine instances it has created such that after the first engine, it can spawn further engines using the first.

Previously we manually managed this array by adding engines to it upon creation, then having [FlutterEngine dealloc] emit a notification such that FlutterEngineGroup can listen for it, and remove instances from the array upon reception of the notification.

Instead, we now use an NSPointerArray of of weak pointers such that pointers are automatically nilled out by ARC after the last strong reference is collected. This eliminates the need for the manual notification during dealloc.

Unfortunately, NSPointerArray is "clever" and assumes that if the array has not been mutated to store a nil pointer since its last compact call, it must not contain a nil pointer and can thus skip compaction. Under ARC, weak pointers are automatically nilled out when the last strong reference is released, so the above heuristic is no longer valid. We work around the issue by storing a nil pointer before calling compact.

See http://www.openradar.me/15396578 for the radar tracking this bug.

I'm not thrilled with the fact that we're replacing one sort of TODO with another, but the code is much simpler and avoids relying on a trip through the notification center, which seems objectively better.

Issue: https://github.com/flutter/flutter/issues/155943
Issue: https://github.com/flutter/flutter/issues/137801

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
Chris Bracken 2024-11-16 19:50:19 -08:00 committed by GitHub
parent 74650a44ff
commit 9a2c95dc83
6 changed files with 15 additions and 42 deletions

View File

@ -84,7 +84,6 @@ NSString* const FlutterDefaultInitialRoute = nil;
#pragma mark - Internal constants #pragma mark - Internal constants
NSString* const kFlutterEngineWillDealloc = @"FlutterEngineWillDealloc";
NSString* const kFlutterKeyDataChannel = @"flutter/keydata"; NSString* const kFlutterKeyDataChannel = @"flutter/keydata";
static constexpr int kNumProfilerSamplesPerSec = 5; static constexpr int kNumProfilerSamplesPerSec = 5;
@ -282,10 +281,6 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
} }
}]; }];
[[NSNotificationCenter defaultCenter] postNotificationName:kFlutterEngineWillDealloc
object:self
userInfo:nil];
// nil out weak references. // nil out weak references.
// TODO(cbracken): https://github.com/flutter/flutter/issues/156222 // TODO(cbracken): https://github.com/flutter/flutter/issues/156222
// Ensure that FlutterEngineRegistrar is using weak pointers, then eliminate this code. // Ensure that FlutterEngineRegistrar is using weak pointers, then eliminate this code.

View File

@ -12,7 +12,7 @@ FLUTTER_ASSERT_ARC
@interface FlutterEngineGroup () @interface FlutterEngineGroup ()
@property(nonatomic, copy) NSString* name; @property(nonatomic, copy) NSString* name;
@property(nonatomic, strong) NSMutableArray<NSValue*>* engines; @property(nonatomic, strong) NSPointerArray* engines;
@property(nonatomic, copy) FlutterDartProject* project; @property(nonatomic, copy) FlutterDartProject* project;
@property(nonatomic, assign) NSUInteger enginesCreatedCount; @property(nonatomic, assign) NSUInteger enginesCreatedCount;
@end @end
@ -23,7 +23,7 @@ FLUTTER_ASSERT_ARC
self = [super init]; self = [super init];
if (self) { if (self) {
_name = [name copy]; _name = [name copy];
_engines = [[NSMutableArray<NSValue*> alloc] init]; _engines = [NSPointerArray weakObjectsPointerArray];
_project = project; _project = project;
} }
return self; return self;
@ -51,6 +51,12 @@ FLUTTER_ASSERT_ARC
NSArray<NSString*>* entrypointArgs = options.entrypointArgs; NSArray<NSString*>* entrypointArgs = options.entrypointArgs;
FlutterEngine* engine; FlutterEngine* engine;
// NSPointerArray is clever and assumes that unless a mutation operation has occurred on it that
// has set one of its values to nil, nothing could have changed and it can skip compaction.
// That's reasonable behaviour on a regular NSPointerArray but not for a weakObjectPointerArray.
// As a workaround, we mutate it first. See: http://www.openradar.me/15396578
[self.engines addPointer:nil];
[self.engines compact];
if (self.engines.count <= 0) { if (self.engines.count <= 0) {
engine = [self makeEngine]; engine = [self makeEngine];
[engine runWithEntrypoint:entrypoint [engine runWithEntrypoint:entrypoint
@ -58,20 +64,13 @@ FLUTTER_ASSERT_ARC
initialRoute:initialRoute initialRoute:initialRoute
entrypointArgs:entrypointArgs]; entrypointArgs:entrypointArgs];
} else { } else {
FlutterEngine* spawner = (FlutterEngine*)[self.engines[0] pointerValue]; FlutterEngine* spawner = (__bridge FlutterEngine*)[self.engines pointerAtIndex:0];
engine = [spawner spawnWithEntrypoint:entrypoint engine = [spawner spawnWithEntrypoint:entrypoint
libraryURI:libraryURI libraryURI:libraryURI
initialRoute:initialRoute initialRoute:initialRoute
entrypointArgs:entrypointArgs]; entrypointArgs:entrypointArgs];
} }
// TODO(cbracken): https://github.com/flutter/flutter/issues/155943 [self.engines addPointer:(__bridge void*)engine];
[self.engines addObject:[NSValue valueWithPointer:(__bridge void*)engine]];
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(onEngineWillBeDealloced:)
name:kFlutterEngineWillDealloc
object:engine];
return engine; return engine;
} }
@ -82,9 +81,4 @@ FLUTTER_ASSERT_ARC
return [[FlutterEngine alloc] initWithName:engineName project:self.project]; return [[FlutterEngine alloc] initWithName:engineName project:self.project];
} }
- (void)onEngineWillBeDealloced:(NSNotification*)notification {
// TODO(cbracken): https://github.com/flutter/flutter/issues/155943
[self.engines removeObject:[NSValue valueWithPointer:(__bridge void*)notification.object]];
}
@end @end

View File

@ -37,11 +37,16 @@ FLUTTER_ASSERT_ARC
} }
- (void)testDeleteLastEngine { - (void)testDeleteLastEngine {
__weak FlutterEngine* weakSpawner;
FlutterEngineGroup* group = [[FlutterEngineGroup alloc] initWithName:@"foo" project:nil]; FlutterEngineGroup* group = [[FlutterEngineGroup alloc] initWithName:@"foo" project:nil];
@autoreleasepool { @autoreleasepool {
FlutterEngine* spawner = [group makeEngineWithEntrypoint:nil libraryURI:nil]; FlutterEngine* spawner = [group makeEngineWithEntrypoint:nil libraryURI:nil];
weakSpawner = spawner;
XCTAssertNotNil(spawner); XCTAssertNotNil(spawner);
XCTAssertNotNil(weakSpawner);
} }
XCTAssertNil(weakSpawner);
FlutterEngine* spawnee = [group makeEngineWithEntrypoint:nil libraryURI:nil]; FlutterEngine* spawnee = [group makeEngineWithEntrypoint:nil libraryURI:nil];
XCTAssertNotNil(spawnee); XCTAssertNotNil(spawnee);
} }

View File

@ -262,23 +262,6 @@ FLUTTER_ASSERT_ARC
XCTAssertNotNil(spawn); XCTAssertNotNil(spawn);
} }
- (void)testDeallocNotification {
XCTestExpectation* deallocNotification = [self expectationWithDescription:@"deallocNotification"];
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
id<NSObject> observer;
@autoreleasepool {
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
observer = [center addObserverForName:kFlutterEngineWillDealloc
object:engine
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* note) {
[deallocNotification fulfill];
}];
}
[self waitForExpectations:@[ deallocNotification ]];
[center removeObserver:observer];
}
- (void)testSetHandlerAfterRun { - (void)testSetHandlerAfterRun {
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"]; XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"];

View File

@ -29,8 +29,6 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
extern NSString* const kFlutterEngineWillDealloc;
@interface FlutterEngine () <FlutterViewEngineDelegate> @interface FlutterEngine () <FlutterViewEngineDelegate>
- (flutter::Shell&)shell; - (flutter::Shell&)shell;

View File

@ -11,8 +11,6 @@
#import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h"
#include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/embedder.h"
extern NSString* const kFlutterEngineWillDealloc;
@class FlutterBinaryMessengerRelay; @class FlutterBinaryMessengerRelay;
namespace flutter { namespace flutter {