Fix iOS platform view not deallocated (#18164)

Co-authored-by: Aaron Clarke <aaclarke@google.com>
diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
index 229e398..0818971 100644
--- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
+++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
@@ -508,10 +508,12 @@
 
 @end
 
-@implementation FlutterPlatformViewSemanticsContainer {
-  SemanticsObject* _semanticsObject;
-  UIView* _platformView;
-}
+@interface FlutterPlatformViewSemanticsContainer ()
+@property(nonatomic, assign) SemanticsObject* semanticsObject;
+@property(nonatomic, strong) UIView* platformView;
+@end
+
+@implementation FlutterPlatformViewSemanticsContainer
 
 // Method declared as unavailable in the interface
 - (instancetype)init {
@@ -531,13 +533,22 @@
     flutter::FlutterPlatformViewsController* controller =
         object.bridge->GetPlatformViewsController();
     if (controller) {
-      _platformView = [controller->GetPlatformViewByID(object.node.platformViewId) view];
+      _platformView = [[controller->GetPlatformViewByID(object.node.platformViewId) view] retain];
     }
-    self.accessibilityElements = @[ _semanticsObject, _platformView ];
   }
   return self;
 }
 
+- (void)dealloc {
+  [_platformView release];
+  _platformView = nil;
+  [super dealloc];
+}
+
+- (NSArray*)accessibilityElements {
+  return @[ _semanticsObject, _platformView ];
+}
+
 - (CGRect)accessibilityFrame {
   return _semanticsObject.accessibilityFrame;
 }
diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
index bc13dfb..08d8069 100644
--- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
+++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
@@ -89,11 +89,11 @@
     if (object.node.IsPlatformViewNode()) {
       FlutterPlatformViewsController* controller = GetPlatformViewsController();
       if (controller) {
-        object.platformViewSemanticsContainer =
-            [[FlutterPlatformViewSemanticsContainer alloc] initWithSemanticsObject:object];
+        object.platformViewSemanticsContainer = [[[FlutterPlatformViewSemanticsContainer alloc]
+            initWithSemanticsObject:object] autorelease];
       }
     } else if (object.platformViewSemanticsContainer) {
-      [object.platformViewSemanticsContainer release];
+      object.platformViewSemanticsContainer = nil;
     }
   }
 
diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
index 6f17747..c242c43 100644
--- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
+++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
@@ -5,11 +5,67 @@
 #import <XCTest/XCTest.h>
 
 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
+#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h"
+#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
 #import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h"
 #import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
 #import "third_party/ocmock/Source/OCMock/OCMock.h"
 
 FLUTTER_ASSERT_NOT_ARC
+@class MockPlatformView;
+static MockPlatformView* gMockPlatformView = nil;
+
+@interface MockPlatformView : UIView
+@end
+@implementation MockPlatformView
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    gMockPlatformView = self;
+  }
+  return self;
+}
+
+- (void)dealloc {
+  gMockPlatformView = nil;
+  [super dealloc];
+}
+
+@end
+
+@interface MockFlutterPlatformView : NSObject <FlutterPlatformView>
+@property(nonatomic, strong) UIView* view;
+@end
+
+@implementation MockFlutterPlatformView
+
+- (instancetype)init {
+  if (self = [super init]) {
+    _view = [[MockPlatformView alloc] init];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [_view release];
+  _view = nil;
+  [super dealloc];
+}
+
+@end
+
+@interface MockFlutterPlatformFactory : NSObject <FlutterPlatformViewFactory>
+@end
+
+@implementation MockFlutterPlatformFactory
+- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
+                                   viewIdentifier:(int64_t)viewId
+                                        arguments:(id _Nullable)args {
+  return [[[MockFlutterPlatformView alloc] init] autorelease];
+}
+
+@end
 
 namespace flutter {
 namespace {
@@ -131,4 +187,55 @@
   OCMVerifyAll(mockFlutterView);
 }
 
+- (void)testSemanticsDeallocated {
+  @autoreleasepool {
+    flutter::MockDelegate mock_delegate;
+    auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
+    flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                                 /*platform=*/thread_task_runner,
+                                 /*raster=*/thread_task_runner,
+                                 /*ui=*/thread_task_runner,
+                                 /*io=*/thread_task_runner);
+    auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+        /*delegate=*/mock_delegate,
+        /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+        /*task_runners=*/runners);
+    id mockFlutterView = OCMClassMock([FlutterView class]);
+    std::string label = "some label";
+
+    auto flutterPlatformViewsController =
+        std::make_unique<flutter::FlutterPlatformViewsController>();
+    flutterPlatformViewsController->SetFlutterView(mockFlutterView);
+
+    MockFlutterPlatformFactory* factory = [[MockFlutterPlatformFactory new] autorelease];
+    flutterPlatformViewsController->RegisterViewFactory(
+        factory, @"MockFlutterPlatformView",
+        FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
+    FlutterResult result = ^(id result) {
+    };
+    flutterPlatformViewsController->OnMethodCall(
+        [FlutterMethodCall
+            methodCallWithMethodName:@"create"
+                           arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
+        result);
+
+    auto bridge = std::make_unique<flutter::AccessibilityBridge>(
+        /*view=*/mockFlutterView,
+        /*platform_view=*/platform_view.get(),
+        /*platform_views_controller=*/flutterPlatformViewsController.get());
+
+    flutter::SemanticsNodeUpdates nodes;
+    flutter::SemanticsNode semantics_node;
+    semantics_node.id = 2;
+    semantics_node.platformViewId = 2;
+    semantics_node.label = label;
+    nodes[kRootNodeId] = semantics_node;
+    flutter::CustomAccessibilityActionUpdates actions;
+    bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
+    XCTAssertNotNil(gMockPlatformView);
+    flutterPlatformViewsController->Reset();
+  }
+  XCTAssertNil(gMockPlatformView);
+}
+
 @end