-
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; | ||
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 | ||
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 () | ||
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; | ||
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 | ||
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 | ||
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)) | ||
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; | ||
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)) { | ||
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)) { | ||
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. 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 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.