Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a504499

Browse files
authored
Migrate FlutterCallbackCache and FlutterKeyboardManager to ARC (#51983)
Smart pointers support ARC as of #47612, and the unit tests were migrated in #48162. Migrate`FlutterCallbackCache` and `FlutterKeyboardManager` from MRC to ARC. These files do not themselves import any MRC files, making them leaf nodes in the dependency graph of MRC files. Doing a few at a time to make the dependency graph manageable, and to easily revert if this causes retain cycles or other memory management issues. Part of flutter/flutter#137801.
1 parent fed19a6 commit a504499

File tree

5 files changed

+50
-138
lines changed

5 files changed

+50
-138
lines changed

shell/platform/darwin/common/framework/Source/FlutterChannelsTest.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ - (void)testResize {
168168
[binaryMessenger stopMocking];
169169
}
170170

171-
- (bool)testSetWarnsOnOverflow {
171+
- (void)testSetWarnsOnOverflow {
172172
NSString* channelName = @"flutter/test";
173173
id binaryMessenger = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger));
174174
id codec = OCMProtocolMock(@protocol(FlutterMethodCodec));

shell/platform/darwin/ios/BUILD.gn

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ source_set("flutter_framework_source_arc") {
5959
public_configs = [ "//flutter:config" ]
6060

6161
sources = [
62+
"framework/Source/FlutterCallbackCache.mm",
63+
"framework/Source/FlutterCallbackCache_Internal.h",
6264
"framework/Source/FlutterEmbedderKeyResponder.h",
6365
"framework/Source/FlutterEmbedderKeyResponder.mm",
6466
"framework/Source/FlutterKeyPrimaryResponder.h",
6567
"framework/Source/FlutterKeySecondaryResponder.h",
68+
"framework/Source/FlutterKeyboardManager.h",
69+
"framework/Source/FlutterKeyboardManager.mm",
6670
"framework/Source/FlutterMetalLayer.h",
6771
"framework/Source/FlutterMetalLayer.mm",
6872
"framework/Source/FlutterRestorationPlugin.h",
@@ -83,7 +87,10 @@ source_set("flutter_framework_source_arc") {
8387
"IOSurface.framework",
8488
]
8589

86-
deps += [ "//flutter/shell/platform/embedder:embedder_as_internal_library" ]
90+
deps += [
91+
"//flutter/lib/ui",
92+
"//flutter/shell/platform/embedder:embedder_as_internal_library",
93+
]
8794
}
8895

8996
source_set("flutter_framework_source") {
@@ -98,8 +105,6 @@ source_set("flutter_framework_source") {
98105
# New files are highly encouraged to be in ARC.
99106
# To add new files in ARC, add them to the `flutter_framework_source_arc` target.
100107
"framework/Source/FlutterAppDelegate.mm",
101-
"framework/Source/FlutterCallbackCache.mm",
102-
"framework/Source/FlutterCallbackCache_Internal.h",
103108
"framework/Source/FlutterChannelKeyResponder.h",
104109
"framework/Source/FlutterChannelKeyResponder.mm",
105110
"framework/Source/FlutterDartProject.mm",
@@ -110,8 +115,6 @@ source_set("flutter_framework_source") {
110115
"framework/Source/FlutterEngineGroup.mm",
111116
"framework/Source/FlutterEngine_Internal.h",
112117
"framework/Source/FlutterHeadlessDartRunner.mm",
113-
"framework/Source/FlutterKeyboardManager.h",
114-
"framework/Source/FlutterKeyboardManager.mm",
115118
"framework/Source/FlutterOverlayView.h",
116119
"framework/Source/FlutterOverlayView.mm",
117120
"framework/Source/FlutterPlatformPlugin.h",

shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,9 @@
77
#include "flutter/fml/logging.h"
88
#include "flutter/lib/ui/plugins/callback_cache.h"
99

10-
@implementation FlutterCallbackInformation
11-
12-
- (void)dealloc {
13-
[_callbackName release];
14-
[_callbackClassName release];
15-
[_callbackLibraryPath release];
16-
[super dealloc];
17-
}
10+
FLUTTER_ASSERT_ARC
1811

12+
@implementation FlutterCallbackInformation
1913
@end
2014

2115
@implementation FlutterCallbackCache
@@ -25,7 +19,7 @@ + (FlutterCallbackInformation*)lookupCallbackInformation:(int64_t)handle {
2519
if (info == nullptr) {
2620
return nil;
2721
}
28-
FlutterCallbackInformation* new_info = [[[FlutterCallbackInformation alloc] init] autorelease];
22+
FlutterCallbackInformation* new_info = [[FlutterCallbackInformation alloc] init];
2923
new_info.callbackName = [NSString stringWithUTF8String:info->name.c_str()];
3024
new_info.callbackClassName = [NSString stringWithUTF8String:info->class_name.c_str()];
3125
new_info.callbackLibraryPath = [NSString stringWithUTF8String:info->library_path.c_str()];

shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
// found in the LICENSE file.
44

55
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h"
6+
67
#include "flutter/fml/platform/darwin/message_loop_darwin.h"
7-
#include "flutter/fml/platform/darwin/weak_nsobject.h"
8+
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
9+
10+
FLUTTER_ASSERT_ARC
811

912
static constexpr CFTimeInterval kDistantFuture = 1.0e10;
1013

@@ -13,13 +16,13 @@ @interface FlutterKeyboardManager ()
1316
/**
1417
* The primary responders added by addPrimaryResponder.
1518
*/
16-
@property(nonatomic, retain, readonly)
19+
@property(nonatomic, copy, readonly)
1720
NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
1821

1922
/**
2023
* The secondary responders added by addSecondaryResponder.
2124
*/
22-
@property(nonatomic, retain, readonly)
25+
@property(nonatomic, copy, readonly)
2326
NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders;
2427

2528
- (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
@@ -28,16 +31,13 @@ - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
2831

2932
@end
3033

31-
@implementation FlutterKeyboardManager {
32-
std::unique_ptr<fml::WeakNSObjectFactory<FlutterKeyboardManager>> _weakFactory;
33-
}
34+
@implementation FlutterKeyboardManager
3435

3536
- (nonnull instancetype)init {
3637
self = [super init];
3738
if (self != nil) {
3839
_primaryResponders = [[NSMutableArray alloc] init];
3940
_secondaryResponders = [[NSMutableArray alloc] init];
40-
_weakFactory = std::make_unique<fml::WeakNSObjectFactory<FlutterKeyboardManager>>(self);
4141
}
4242
return self;
4343
}
@@ -50,24 +50,6 @@ - (void)addSecondaryResponder:(nonnull id<FlutterKeySecondaryResponder>)responde
5050
[_secondaryResponders addObject:responder];
5151
}
5252

53-
- (void)dealloc {
54-
// It will be destroyed and invalidate its weak pointers
55-
// before any other members are destroyed.
56-
_weakFactory.reset();
57-
58-
[_primaryResponders removeAllObjects];
59-
[_secondaryResponders removeAllObjects];
60-
[_primaryResponders release];
61-
[_secondaryResponders release];
62-
_primaryResponders = nil;
63-
_secondaryResponders = nil;
64-
[super dealloc];
65-
}
66-
67-
- (fml::WeakNSObject<FlutterKeyboardManager>)getWeakNSObject {
68-
return _weakFactory->GetWeakNSObject();
69-
}
70-
7153
- (void)handlePress:(nonnull FlutterUIPressProxy*)press
7254
nextAction:(nonnull void (^)())next API_AVAILABLE(ios(13.4)) {
7355
if (@available(iOS 13.4, *)) {
@@ -89,7 +71,7 @@ - (void)handlePress:(nonnull FlutterUIPressProxy*)press
8971
// encounter.
9072
NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added.");
9173

92-
__block auto weakSelf = [self getWeakNSObject];
74+
__block __weak __typeof(self) weakSelf = self;
9375
__block NSUInteger unreplied = [self.primaryResponders count];
9476
__block BOOL anyHandled = false;
9577
FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
@@ -98,7 +80,7 @@ - (void)handlePress:(nonnull FlutterUIPressProxy*)press
9880
anyHandled = anyHandled || handled;
9981
if (unreplied == 0) {
10082
if (!anyHandled && weakSelf) {
101-
[weakSelf.get() dispatchToSecondaryResponders:press complete:completeCallback];
83+
[weakSelf dispatchToSecondaryResponders:press complete:completeCallback];
10284
} else {
10385
completeCallback(true, press);
10486
}

shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm

Lines changed: 29 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -23,43 +23,6 @@
2323

2424
using namespace flutter::testing;
2525

26-
/// Sometimes we have to use a custom mock to avoid retain cycles in ocmock.
27-
@interface FlutterEnginePartialMock : FlutterEngine
28-
@property(nonatomic, strong) FlutterBasicMessageChannel* lifecycleChannel;
29-
@property(nonatomic, weak) FlutterViewController* viewController;
30-
@property(nonatomic, assign) BOOL didCallNotifyLowMemory;
31-
@end
32-
33-
@interface FlutterEngine ()
34-
- (BOOL)createShell:(NSString*)entrypoint
35-
libraryURI:(NSString*)libraryURI
36-
initialRoute:(NSString*)initialRoute;
37-
- (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet;
38-
@end
39-
40-
@interface FlutterEngine (TestLowMemory)
41-
- (void)notifyLowMemory;
42-
@end
43-
44-
extern NSNotificationName const FlutterViewControllerWillDealloc;
45-
46-
/// A simple mock class for FlutterEngine.
47-
///
48-
/// OCMockClass can't be used for FlutterEngine sometimes because OCMock retains arguments to
49-
/// invocations and since the init for FlutterViewController calls a method on the
50-
/// FlutterEngine it creates a retain cycle that stops us from testing behaviors related to
51-
/// deleting FlutterViewControllers.
52-
@interface MockEngine : NSObject
53-
@end
54-
55-
@interface FlutterKeyboardManagerUnittestsObjC : NSObject
56-
- (bool)nextResponderShouldThrowOnPressesEnded;
57-
- (bool)singlePrimaryResponder;
58-
- (bool)doublePrimaryResponder;
59-
- (bool)singleSecondaryResponder;
60-
- (bool)emptyNextResponder;
61-
@end
62-
6326
namespace {
6427

6528
typedef void (^KeyCallbackSetter)(FlutterUIPressProxy* press, FlutterAsyncKeyCallback callback)
@@ -68,52 +31,28 @@ typedef void (^KeyCallbackSetter)(FlutterUIPressProxy* press, FlutterAsyncKeyCal
6831

6932
} // namespace
7033

34+
// These tests were designed to run on iOS 13.4 or later.
35+
API_AVAILABLE(ios(13.4))
7136
@interface FlutterKeyboardManagerTest : XCTestCase
72-
@property(nonatomic, strong) id mockEngine;
73-
- (FlutterViewController*)mockOwnerWithPressesBeginOnlyNext API_AVAILABLE(ios(13.4));
7437
@end
7538

7639
@implementation FlutterKeyboardManagerTest
7740

78-
- (void)setUp {
79-
// All of these tests were designed to run on iOS 13.4 or later.
80-
if (@available(iOS 13.4, *)) {
81-
} else {
82-
XCTSkip(@"Required API not present for test.");
83-
}
84-
85-
[super setUp];
86-
self.mockEngine = OCMClassMock([FlutterEngine class]);
87-
}
88-
89-
- (void)tearDown {
90-
// We stop mocking here to avoid retain cycles that stop
91-
// FlutterViewControllers from deallocing.
92-
[self.mockEngine stopMocking];
93-
self.mockEngine = nil;
94-
[super tearDown];
95-
}
96-
97-
- (id)checkKeyDownEvent:(UIKeyboardHIDUsage)keyCode API_AVAILABLE(ios(13.4)) {
98-
return [OCMArg checkWithBlock:^BOOL(id value) {
99-
if (![value isKindOfClass:[FlutterUIPressProxy class]]) {
100-
return NO;
101-
}
102-
FlutterUIPressProxy* press = value;
103-
return press.key.keyCode == keyCode;
104-
}];
105-
}
106-
107-
- (id<FlutterKeyPrimaryResponder>)mockPrimaryResponder:(KeyCallbackSetter)callbackSetter
108-
API_AVAILABLE(ios(13.4)) {
41+
- (id<FlutterKeyPrimaryResponder>)mockPrimaryResponder:(KeyCallbackSetter)callbackSetter {
10942
id<FlutterKeyPrimaryResponder> mock =
11043
OCMStrictProtocolMock(@protocol(FlutterKeyPrimaryResponder));
11144
OCMStub([mock handlePress:[OCMArg any] callback:[OCMArg any]])
11245
.andDo((^(NSInvocation* invocation) {
113-
FlutterUIPressProxy* press;
114-
FlutterAsyncKeyCallback callback;
115-
[invocation getArgument:&press atIndex:2];
116-
[invocation getArgument:&callback atIndex:3];
46+
__unsafe_unretained FlutterUIPressProxy* pressUnsafe;
47+
__unsafe_unretained FlutterAsyncKeyCallback callbackUnsafe;
48+
49+
[invocation getArgument:&pressUnsafe atIndex:2];
50+
[invocation getArgument:&callbackUnsafe atIndex:3];
51+
52+
// Retain the unretained parameters so they can
53+
// be run in the perform block when this invocation goes out of scope.
54+
FlutterUIPressProxy* press = pressUnsafe;
55+
FlutterAsyncKeyCallback callback = callbackUnsafe;
11756
CFRunLoopPerformBlock(CFRunLoopGetCurrent(),
11857
fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, ^() {
11958
callbackSetter(press, callback);
@@ -122,8 +61,7 @@ - (id)checkKeyDownEvent:(UIKeyboardHIDUsage)keyCode API_AVAILABLE(ios(13.4)) {
12261
return mock;
12362
}
12463

125-
- (id<FlutterKeySecondaryResponder>)mockSecondaryResponder:(BoolGetter)resultGetter
126-
API_AVAILABLE(ios(13.4)) {
64+
- (id<FlutterKeySecondaryResponder>)mockSecondaryResponder:(BoolGetter)resultGetter {
12765
id<FlutterKeySecondaryResponder> mock =
12866
OCMStrictProtocolMock(@protocol(FlutterKeySecondaryResponder));
12967
OCMStub([mock handlePress:[OCMArg any]]).andDo((^(NSInvocation* invocation) {
@@ -133,32 +71,27 @@ - (id)checkKeyDownEvent:(UIKeyboardHIDUsage)keyCode API_AVAILABLE(ios(13.4)) {
13371
return mock;
13472
}
13573

136-
- (FlutterViewController*)mockOwnerWithPressesBeginOnlyNext API_AVAILABLE(ios(13.4)) {
74+
- (void)testNextResponderShouldThrowOnPressesEnded {
13775
// The nextResponder is a strict mock and hasn't stubbed pressesEnded.
13876
// An error will be thrown on pressesEnded.
13977
UIResponder* nextResponder = OCMStrictClassMock([UIResponder class]);
140-
OCMStub([nextResponder pressesBegan:[OCMArg any] withEvent:[OCMArg any]]).andDo(nil);
78+
OCMStub([nextResponder pressesBegan:OCMOCK_ANY withEvent:OCMOCK_ANY]);
14179

142-
FlutterViewController* viewController =
143-
[[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil];
80+
id mockEngine = OCMClassMock([FlutterEngine class]);
81+
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
82+
nibName:nil
83+
bundle:nil];
14484
FlutterViewController* owner = OCMPartialMock(viewController);
14585
OCMStub([owner nextResponder]).andReturn(nextResponder);
146-
return owner;
147-
}
14886

149-
// Verify that the nextResponder returned from mockOwnerWithPressesBeginOnlyNext()
150-
// throws exception when pressesEnded is called.
151-
- (bool)testNextResponderShouldThrowOnPressesEnded API_AVAILABLE(ios(13.4)) {
152-
FlutterViewController* owner = [self mockOwnerWithPressesBeginOnlyNext];
153-
@try {
154-
[owner.nextResponder pressesEnded:[NSSet init] withEvent:[[UIPressesEvent alloc] init]];
155-
return false;
156-
} @catch (...) {
157-
return true;
158-
}
87+
XCTAssertThrowsSpecificNamed([owner.nextResponder pressesEnded:[[NSSet alloc] init]
88+
withEvent:[[UIPressesEvent alloc] init]],
89+
NSException, NSInternalInconsistencyException);
90+
91+
[mockEngine stopMocking];
15992
}
16093

161-
- (void)testSinglePrimaryResponder API_AVAILABLE(ios(13.4)) {
94+
- (void)testSinglePrimaryResponder {
16295
FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init];
16396
__block BOOL primaryResponse = FALSE;
16497
__block int callbackCount = 0;
@@ -190,7 +123,7 @@ - (void)testSinglePrimaryResponder API_AVAILABLE(ios(13.4)) {
190123
XCTAssertFalse(completeHandled);
191124
}
192125

193-
- (void)testDoublePrimaryResponder API_AVAILABLE(ios(13.4)) {
126+
- (void)testDoublePrimaryResponder {
194127
FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init];
195128

196129
__block BOOL callback1Response = FALSE;
@@ -253,7 +186,7 @@ - (void)testDoublePrimaryResponder API_AVAILABLE(ios(13.4)) {
253186
XCTAssertFalse(somethingWasHandled);
254187
}
255188

256-
- (void)testSingleSecondaryResponder API_AVAILABLE(ios(13.4)) {
189+
- (void)testSingleSecondaryResponder {
257190
FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init];
258191

259192
__block BOOL primaryResponse = FALSE;
@@ -308,7 +241,7 @@ - (void)testSingleSecondaryResponder API_AVAILABLE(ios(13.4)) {
308241
XCTAssertFalse(completeHandled);
309242
}
310243

311-
- (void)testEventsProcessedSequentially API_AVAILABLE(ios(13.4)) {
244+
- (void)testEventsProcessedSequentially {
312245
constexpr UIKeyboardHIDUsage keyId1 = (UIKeyboardHIDUsage)0x50;
313246
constexpr UIKeyboardHIDUsage keyId2 = (UIKeyboardHIDUsage)0x51;
314247
FlutterUIPressProxy* event1 = keyDownEvent(keyId1);

0 commit comments

Comments
 (0)