diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm index a58b1c0bf4..439ab69838 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm @@ -50,6 +50,7 @@ extern CFTimeInterval display_link_target; BOOL _displayLinkForcedMaxRate; } +- (void)onDisplayLink:(CADisplayLink*)link; - (void)presentTexture:(FlutterTexture*)texture; - (void)returnTexture:(FlutterTexture*)texture; @@ -163,6 +164,26 @@ extern CFTimeInterval display_link_target; @end +@interface FlutterMetalLayerDisplayLinkProxy : NSObject { + __weak FlutterMetalLayer* _layer; +} + +@end + +@implementation FlutterMetalLayerDisplayLinkProxy +- (instancetype)initWithLayer:(FlutterMetalLayer*)layer { + if (self = [super init]) { + _layer = layer; + } + return self; +} + +- (void)onDisplayLink:(CADisplayLink*)link { + [_layer onDisplayLink:link]; +} + +@end + @implementation FlutterMetalLayer @synthesize preferredDevice = _preferredDevice; @@ -179,7 +200,9 @@ extern CFTimeInterval display_link_target; self.pixelFormat = MTLPixelFormatBGRA8Unorm; _availableTextures = [[NSMutableSet alloc] init]; - _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)]; + FlutterMetalLayerDisplayLinkProxy* proxy = + [[FlutterMetalLayerDisplayLinkProxy alloc] initWithLayer:self]; + _displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(onDisplayLink:)]; [self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:NO]; [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] addObserver:self @@ -190,6 +213,11 @@ extern CFTimeInterval display_link_target; return self; } +- (void)dealloc { + [_displayLink invalidate]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + - (void)setMaxRefreshRate:(double)refreshRate forceMax:(BOOL)forceMax { // This is copied from vsync_waiter_ios.mm. The vsync waiter has display link scheduled on UI // thread which does not trigger actual core animation frame. As a workaround FlutterMetalLayer diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayerTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayerTest.mm index c877c41d9d..034ae62115 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayerTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayerTest.mm @@ -270,4 +270,24 @@ [self removeMetalLayer:layer]; } +- (void)testDealloc { + __weak FlutterMetalLayer* weakLayer; + @autoreleasepool { + FlutterMetalLayer* layer = [self addMetalLayer]; + weakLayer = layer; + TestCompositor* compositor = [[TestCompositor alloc] initWithLayer:layer]; + + id drawable = [layer nextDrawable]; + BAIL_IF_NO_DRAWABLE(drawable); + [drawable present]; + [compositor commitTransaction]; + + [self removeMetalLayer:layer]; + // Deallocating the layer after removing is not synchronous. + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, YES); + } + + XCTAssertNil(weakLayer); +} + @end