-
Notifications
You must be signed in to change notification settings - Fork 6k
Migrate FlutterCallbackCache and FlutterKeyboardManager to ARC #51983
Changes from all commits
cfae8b6
bbb9620
41c6cfe
e431d11
2f6a591
25bfa11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,11 @@ | |
| // found in the LICENSE file. | ||
|
|
||
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h" | ||
|
|
||
| #include "flutter/fml/platform/darwin/message_loop_darwin.h" | ||
| #include "flutter/fml/platform/darwin/weak_nsobject.h" | ||
| #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" | ||
|
|
||
| FLUTTER_ASSERT_ARC | ||
|
|
||
| static constexpr CFTimeInterval kDistantFuture = 1.0e10; | ||
|
|
||
|
|
@@ -13,13 +16,13 @@ @interface FlutterKeyboardManager () | |
| /** | ||
| * The primary responders added by addPrimaryResponder. | ||
| */ | ||
| @property(nonatomic, retain, readonly) | ||
| @property(nonatomic, copy, readonly) | ||
| NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders; | ||
|
|
||
| /** | ||
| * The secondary responders added by addSecondaryResponder. | ||
| */ | ||
| @property(nonatomic, retain, readonly) | ||
| @property(nonatomic, copy, readonly) | ||
| NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders; | ||
|
|
||
| - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press | ||
|
|
@@ -28,16 +31,13 @@ - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press | |
|
|
||
| @end | ||
|
|
||
| @implementation FlutterKeyboardManager { | ||
| std::unique_ptr<fml::WeakNSObjectFactory<FlutterKeyboardManager>> _weakFactory; | ||
| } | ||
| @implementation FlutterKeyboardManager | ||
|
|
||
| - (nonnull instancetype)init { | ||
| self = [super init]; | ||
| if (self != nil) { | ||
| _primaryResponders = [[NSMutableArray alloc] init]; | ||
| _secondaryResponders = [[NSMutableArray alloc] init]; | ||
| _weakFactory = std::make_unique<fml::WeakNSObjectFactory<FlutterKeyboardManager>>(self); | ||
| } | ||
| return self; | ||
| } | ||
|
|
@@ -50,24 +50,6 @@ - (void)addSecondaryResponder:(nonnull id<FlutterKeySecondaryResponder>)responde | |
| [_secondaryResponders addObject:responder]; | ||
| } | ||
|
|
||
| - (void)dealloc { | ||
| // It will be destroyed and invalidate its weak pointers | ||
| // before any other members are destroyed. | ||
| _weakFactory.reset(); | ||
|
|
||
| [_primaryResponders removeAllObjects]; | ||
| [_secondaryResponders removeAllObjects]; | ||
| [_primaryResponders release]; | ||
| [_secondaryResponders release]; | ||
| _primaryResponders = nil; | ||
| _secondaryResponders = nil; | ||
| [super dealloc]; | ||
| } | ||
|
|
||
| - (fml::WeakNSObject<FlutterKeyboardManager>)getWeakNSObject { | ||
| return _weakFactory->GetWeakNSObject(); | ||
| } | ||
|
|
||
| - (void)handlePress:(nonnull FlutterUIPressProxy*)press | ||
| nextAction:(nonnull void (^)())next API_AVAILABLE(ios(13.4)) { | ||
| if (@available(iOS 13.4, *)) { | ||
|
|
@@ -89,7 +71,7 @@ - (void)handlePress:(nonnull FlutterUIPressProxy*)press | |
| // encounter. | ||
| NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added."); | ||
|
|
||
| __block auto weakSelf = [self getWeakNSObject]; | ||
| __block __weak __typeof(self) weakSelf = self; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the |
||
| __block NSUInteger unreplied = [self.primaryResponders count]; | ||
| __block BOOL anyHandled = false; | ||
| FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) { | ||
|
|
@@ -98,7 +80,7 @@ - (void)handlePress:(nonnull FlutterUIPressProxy*)press | |
| anyHandled = anyHandled || handled; | ||
| if (unreplied == 0) { | ||
| if (!anyHandled && weakSelf) { | ||
| [weakSelf.get() dispatchToSecondaryResponders:press complete:completeCallback]; | ||
| [weakSelf dispatchToSecondaryResponders:press complete:completeCallback]; | ||
| } else { | ||
| completeCallback(true, press); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,43 +23,6 @@ | |
|
|
||
| using namespace flutter::testing; | ||
|
|
||
| /// Sometimes we have to use a custom mock to avoid retain cycles in ocmock. | ||
| @interface FlutterEnginePartialMock : FlutterEngine | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead code. |
||
| @property(nonatomic, strong) FlutterBasicMessageChannel* lifecycleChannel; | ||
| @property(nonatomic, weak) FlutterViewController* viewController; | ||
| @property(nonatomic, assign) BOOL didCallNotifyLowMemory; | ||
| @end | ||
|
|
||
| @interface FlutterEngine () | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead code. |
||
| - (BOOL)createShell:(NSString*)entrypoint | ||
| libraryURI:(NSString*)libraryURI | ||
| initialRoute:(NSString*)initialRoute; | ||
| - (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet; | ||
| @end | ||
|
|
||
| @interface FlutterEngine (TestLowMemory) | ||
| - (void)notifyLowMemory; | ||
| @end | ||
|
|
||
| extern NSNotificationName const FlutterViewControllerWillDealloc; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead code. |
||
|
|
||
| /// A simple mock class for FlutterEngine. | ||
| /// | ||
| /// OCMockClass can't be used for FlutterEngine sometimes because OCMock retains arguments to | ||
| /// invocations and since the init for FlutterViewController calls a method on the | ||
| /// FlutterEngine it creates a retain cycle that stops us from testing behaviors related to | ||
| /// deleting FlutterViewControllers. | ||
| @interface MockEngine : NSObject | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead code. |
||
| @end | ||
|
|
||
| @interface FlutterKeyboardManagerUnittestsObjC : NSObject | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what this is, there's no implementation. |
||
| - (bool)nextResponderShouldThrowOnPressesEnded; | ||
| - (bool)singlePrimaryResponder; | ||
| - (bool)doublePrimaryResponder; | ||
| - (bool)singleSecondaryResponder; | ||
| - (bool)emptyNextResponder; | ||
| @end | ||
|
|
||
| namespace { | ||
|
|
||
| typedef void (^KeyCallbackSetter)(FlutterUIPressProxy* press, FlutterAsyncKeyCallback callback) | ||
|
|
@@ -68,52 +31,28 @@ typedef void (^KeyCallbackSetter)(FlutterUIPressProxy* press, FlutterAsyncKeyCal | |
|
|
||
| } // namespace | ||
|
|
||
| // These tests were designed to run on iOS 13.4 or later. | ||
| API_AVAILABLE(ios(13.4)) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Putting the availability on the class |
||
| @interface FlutterKeyboardManagerTest : XCTestCase | ||
| @property(nonatomic, strong) id mockEngine; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved this into a local in the one remaining spot the engine is mocked. |
||
| - (FlutterViewController*)mockOwnerWithPressesBeginOnlyNext API_AVAILABLE(ios(13.4)); | ||
| @end | ||
|
|
||
| @implementation FlutterKeyboardManagerTest | ||
|
|
||
| - (void)setUp { | ||
| // All of these tests were designed to run on iOS 13.4 or later. | ||
| if (@available(iOS 13.4, *)) { | ||
| } else { | ||
| XCTSkip(@"Required API not present for test."); | ||
| } | ||
|
|
||
| [super setUp]; | ||
| self.mockEngine = OCMClassMock([FlutterEngine class]); | ||
| } | ||
|
|
||
| - (void)tearDown { | ||
| // We stop mocking here to avoid retain cycles that stop | ||
| // FlutterViewControllers from deallocing. | ||
| [self.mockEngine stopMocking]; | ||
| self.mockEngine = nil; | ||
| [super tearDown]; | ||
| } | ||
|
|
||
| - (id)checkKeyDownEvent:(UIKeyboardHIDUsage)keyCode API_AVAILABLE(ios(13.4)) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead code. |
||
| return [OCMArg checkWithBlock:^BOOL(id value) { | ||
| if (![value isKindOfClass:[FlutterUIPressProxy class]]) { | ||
| return NO; | ||
| } | ||
| FlutterUIPressProxy* press = value; | ||
| return press.key.keyCode == keyCode; | ||
| }]; | ||
| } | ||
|
|
||
| - (id<FlutterKeyPrimaryResponder>)mockPrimaryResponder:(KeyCallbackSetter)callbackSetter | ||
| API_AVAILABLE(ios(13.4)) { | ||
| - (id<FlutterKeyPrimaryResponder>)mockPrimaryResponder:(KeyCallbackSetter)callbackSetter { | ||
| id<FlutterKeyPrimaryResponder> mock = | ||
| OCMStrictProtocolMock(@protocol(FlutterKeyPrimaryResponder)); | ||
| OCMStub([mock handlePress:[OCMArg any] callback:[OCMArg any]]) | ||
| .andDo((^(NSInvocation* invocation) { | ||
| FlutterUIPressProxy* press; | ||
| FlutterAsyncKeyCallback callback; | ||
| [invocation getArgument:&press atIndex:2]; | ||
| [invocation getArgument:&callback atIndex:3]; | ||
| __unsafe_unretained FlutterUIPressProxy* pressUnsafe; | ||
| __unsafe_unretained FlutterAsyncKeyCallback callbackUnsafe; | ||
|
|
||
| [invocation getArgument:&pressUnsafe atIndex:2]; | ||
| [invocation getArgument:&callbackUnsafe atIndex:3]; | ||
|
|
||
| // Retain the unretained parameters so they can | ||
| // be run in the perform block when this invocation goes out of scope. | ||
| FlutterUIPressProxy* press = pressUnsafe; | ||
| FlutterAsyncKeyCallback callback = callbackUnsafe; | ||
| CFRunLoopPerformBlock(CFRunLoopGetCurrent(), | ||
| fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, ^() { | ||
| callbackSetter(press, callback); | ||
|
|
@@ -122,8 +61,7 @@ - (id)checkKeyDownEvent:(UIKeyboardHIDUsage)keyCode API_AVAILABLE(ios(13.4)) { | |
| return mock; | ||
| } | ||
|
|
||
| - (id<FlutterKeySecondaryResponder>)mockSecondaryResponder:(BoolGetter)resultGetter | ||
| API_AVAILABLE(ios(13.4)) { | ||
| - (id<FlutterKeySecondaryResponder>)mockSecondaryResponder:(BoolGetter)resultGetter { | ||
| id<FlutterKeySecondaryResponder> mock = | ||
| OCMStrictProtocolMock(@protocol(FlutterKeySecondaryResponder)); | ||
| OCMStub([mock handlePress:[OCMArg any]]).andDo((^(NSInvocation* invocation) { | ||
|
|
@@ -133,32 +71,27 @@ - (id)checkKeyDownEvent:(UIKeyboardHIDUsage)keyCode API_AVAILABLE(ios(13.4)) { | |
| return mock; | ||
| } | ||
|
|
||
| - (FlutterViewController*)mockOwnerWithPressesBeginOnlyNext API_AVAILABLE(ios(13.4)) { | ||
| - (void)testNextResponderShouldThrowOnPressesEnded { | ||
| // The nextResponder is a strict mock and hasn't stubbed pressesEnded. | ||
| // An error will be thrown on pressesEnded. | ||
| UIResponder* nextResponder = OCMStrictClassMock([UIResponder class]); | ||
| OCMStub([nextResponder pressesBegan:[OCMArg any] withEvent:[OCMArg any]]).andDo(nil); | ||
| OCMStub([nextResponder pressesBegan:OCMOCK_ANY withEvent:OCMOCK_ANY]); | ||
|
|
||
| FlutterViewController* viewController = | ||
| [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; | ||
| id mockEngine = OCMClassMock([FlutterEngine class]); | ||
| FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine | ||
| nibName:nil | ||
| bundle:nil]; | ||
| FlutterViewController* owner = OCMPartialMock(viewController); | ||
| OCMStub([owner nextResponder]).andReturn(nextResponder); | ||
| return owner; | ||
| } | ||
|
|
||
| // Verify that the nextResponder returned from mockOwnerWithPressesBeginOnlyNext() | ||
| // throws exception when pressesEnded is called. | ||
| - (bool)testNextResponderShouldThrowOnPressesEnded API_AVAILABLE(ios(13.4)) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wasn't actually running because the return type wasn't void.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we need a custom CI check on this? It seems like anything in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filed flutter/flutter#146671 |
||
| FlutterViewController* owner = [self mockOwnerWithPressesBeginOnlyNext]; | ||
| @try { | ||
| [owner.nextResponder pressesEnded:[NSSet init] withEvent:[[UIPressesEvent alloc] init]]; | ||
| return false; | ||
| } @catch (...) { | ||
| return true; | ||
| } | ||
| XCTAssertThrowsSpecificNamed([owner.nextResponder pressesEnded:[[NSSet alloc] init] | ||
| withEvent:[[UIPressesEvent alloc] init]], | ||
| NSException, NSInternalInconsistencyException); | ||
|
|
||
| [mockEngine stopMocking]; | ||
| } | ||
|
|
||
| - (void)testSinglePrimaryResponder API_AVAILABLE(ios(13.4)) { | ||
| - (void)testSinglePrimaryResponder { | ||
| FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init]; | ||
| __block BOOL primaryResponse = FALSE; | ||
| __block int callbackCount = 0; | ||
|
|
@@ -190,7 +123,7 @@ - (void)testSinglePrimaryResponder API_AVAILABLE(ios(13.4)) { | |
| XCTAssertFalse(completeHandled); | ||
| } | ||
|
|
||
| - (void)testDoublePrimaryResponder API_AVAILABLE(ios(13.4)) { | ||
| - (void)testDoublePrimaryResponder { | ||
| FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init]; | ||
|
|
||
| __block BOOL callback1Response = FALSE; | ||
|
|
@@ -253,7 +186,7 @@ - (void)testDoublePrimaryResponder API_AVAILABLE(ios(13.4)) { | |
| XCTAssertFalse(somethingWasHandled); | ||
| } | ||
|
|
||
| - (void)testSingleSecondaryResponder API_AVAILABLE(ios(13.4)) { | ||
| - (void)testSingleSecondaryResponder { | ||
| FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init]; | ||
|
|
||
| __block BOOL primaryResponse = FALSE; | ||
|
|
@@ -308,7 +241,7 @@ - (void)testSingleSecondaryResponder API_AVAILABLE(ios(13.4)) { | |
| XCTAssertFalse(completeHandled); | ||
| } | ||
|
|
||
| - (void)testEventsProcessedSequentially API_AVAILABLE(ios(13.4)) { | ||
| - (void)testEventsProcessedSequentially { | ||
| constexpr UIKeyboardHIDUsage keyId1 = (UIKeyboardHIDUsage)0x50; | ||
| constexpr UIKeyboardHIDUsage keyId2 = (UIKeyboardHIDUsage)0x51; | ||
| FlutterUIPressProxy* event1 = keyDownEvent(keyId1); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated, but noticed this wasn't running because the signature wasn't void.