From 299e6162a29606f99d23d3357c40c3e911ce8130 Mon Sep 17 00:00:00 2001 From: misos1 <30872003+misos1@users.noreply.github.com> Date: Sun, 4 Aug 2024 03:09:21 +0200 Subject: [PATCH] [camera_avfoundation] dealloc camera on dispose (#7211) When is camera disposed in `disposeCamera` it calls `close` and leaves `_camera` untouched so after another call to `sessionQueueCreateCameraWithName` it calls `close` second time and only here is `_camera` disposed by assigning new value into that variable. Calling `close` 2x may be unexpected and prone to later errors if someone adds here something which expects that it will be called only once. Also having dangling `_camera` after dispose (without creating new one) can have other unintended consequences although maybe benign for now. For example there is `[_motionManager stopAccelerometerUpdates]` which is called only in `dealloc`. --- .../camera/camera_avfoundation/CHANGELOG.md | 4 ++ .../RunnerTests/CameraMethodChannelTests.m | 38 +++++++++++++++++++ .../camera_avfoundation/CameraPlugin.m | 1 + .../camera/camera_avfoundation/pubspec.yaml | 2 +- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 0fead073feab..d4f8e8a946a6 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.17+3 + +* Fixes deallocation of camera on dispose. + ## 0.9.17+2 * Fixes stopVideoRecording waiting indefinitely and lag at start of video. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m index 0558e733f26f..4df1994699df 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m @@ -50,4 +50,42 @@ - (void)testCreate_ShouldCallResultOnMainThread { XCTAssertNotNil(resultValue); } +- (void)testDisposeShouldDeallocCamera { + CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; + + id avCaptureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([avCaptureDeviceInputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg anyObjectRef]]) + .andReturn([AVCaptureInput alloc]); + + id avCaptureSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([avCaptureSessionMock alloc]).andReturn(avCaptureSessionMock); + OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + XCTestExpectation *createExpectation = + [self expectationWithDescription:@"create's result block must be called"]; + [camera createCameraOnSessionQueueWithName:@"acamera" + settings:[FCPPlatformMediaSettings + makeWithResolutionPreset: + FCPPlatformResolutionPresetMedium + framesPerSecond:nil + videoBitrate:nil + audioBitrate:nil + enableAudio:YES] + completion:^(NSNumber *_Nullable result, + FlutterError *_Nullable error) { + [createExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; + XCTAssertNotNil(camera.camera); + + XCTestExpectation *disposeExpectation = + [self expectationWithDescription:@"dispose's result block must be called"]; + [camera disposeCamera:0 + completion:^(FlutterError *_Nullable error) { + [disposeExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; + XCTAssertNil(camera.camera, @"camera should be deallocated after dispose"); +} + @end diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m index 151883c71e91..de208fd560ef 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m @@ -416,6 +416,7 @@ - (void)disposeCamera:(NSInteger)cameraId __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ [weakSelf.camera close]; + weakSelf.camera = nil; completion(nil); }); } diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 4444026a41fe..e93474a40a22 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.17+2 +version: 0.9.17+3 environment: sdk: ^3.2.3