Fix iOS embedder memory management and other analyzer warnings (flutter/engine#29623)

This commit is contained in:
Jenn Magder 2021-11-12 13:16:01 -08:00 committed by GitHub
parent e4aec6397c
commit bcce079911
23 changed files with 126 additions and 50 deletions

View File

@ -10,7 +10,7 @@
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
if (!_sharedInstance) {
_sharedInstance = [FlutterBinaryCodec new];
_sharedInstance = [[FlutterBinaryCodec alloc] init];
}
return _sharedInstance;
}
@ -29,7 +29,7 @@
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
if (!_sharedInstance) {
_sharedInstance = [FlutterStringCodec new];
_sharedInstance = [[FlutterStringCodec alloc] init];
}
return _sharedInstance;
}
@ -54,7 +54,7 @@
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
if (!_sharedInstance) {
_sharedInstance = [FlutterJSONMessageCodec new];
_sharedInstance = [[FlutterJSONMessageCodec alloc] init];
}
return _sharedInstance;
}
@ -109,7 +109,7 @@
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
if (!_sharedInstance) {
_sharedInstance = [FlutterJSONMethodCodec new];
_sharedInstance = [[FlutterJSONMethodCodec alloc] init];
}
return _sharedInstance;
}

View File

@ -18,15 +18,15 @@ FLUTTER_DARWIN_EXPORT
/**
* The name of the callback.
*/
@property(retain) NSString* callbackName;
@property(copy) NSString* callbackName;
/**
* The class name of the callback.
*/
@property(retain) NSString* callbackClassName;
@property(copy) NSString* callbackClassName;
/**
* The library path of the callback.
*/
@property(retain) NSString* callbackLibraryPath;
@property(copy) NSString* callbackLibraryPath;
@end
/**

View File

@ -34,6 +34,7 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
- (void)dealloc {
[_lifeCycleDelegate release];
[_rootFlutterViewControllerGetter release];
[_window release];
[super dealloc];
}

View File

@ -121,6 +121,22 @@ FLUTTER_ASSERT_ARC
arguments:@"/custom/route#fragment"]);
}
- (void)testReleasesWindowOnDealloc {
__weak UIWindow* weakWindow;
@autoreleasepool {
id mockWindow = OCMClassMock([UIWindow class]);
FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init];
appDelegate.window = mockWindow;
weakWindow = mockWindow;
XCTAssertNotNil(weakWindow);
[mockWindow stopMocking];
mockWindow = nil;
appDelegate = nil;
}
// App delegate has released the window.
XCTAssertNil(weakWindow);
}
#pragma mark - Deep linking
- (void)testUniversalLinkPushRoute {

View File

@ -7,6 +7,14 @@
#include "flutter/lib/ui/plugins/callback_cache.h"
@implementation FlutterCallbackInformation
- (void)dealloc {
[_callbackName release];
[_callbackClassName release];
[_callbackLibraryPath release];
[super dealloc];
}
@end
@implementation FlutterCallbackCache

View File

@ -273,7 +273,7 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) {
if (exceptionDomains == nil) {
return @"";
}
NSMutableArray* networkConfigArray = [[NSMutableArray alloc] init];
NSMutableArray* networkConfigArray = [[[NSMutableArray alloc] init] autorelease];
for (NSString* domain in exceptionDomains) {
NSDictionary* domainConfiguration = [exceptionDomains objectForKey:domain];
// Default value is false.
@ -288,7 +288,7 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) {
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:networkConfigArray
options:0
error:NULL];
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
}
+ (bool)allowsArbitraryLoads:(NSDictionary*)appTransportSecurity {

View File

@ -349,7 +349,7 @@ void HandleResponse(bool handled, void* user_data);
*
* Set by the initializer.
*/
@property(nonatomic) FlutterSendKeyEvent sendEvent;
@property(nonatomic, copy, readonly) FlutterSendKeyEvent sendEvent;
/**
* A map of pressed keys.
@ -357,7 +357,7 @@ void HandleResponse(bool handled, void* user_data);
* The keys of the dictionary are physical keys, while the values are the logical keys
* of the key down event.
*/
@property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>* pressingRecords;
@property(nonatomic, retain, readonly) NSMutableDictionary<NSNumber*, NSNumber*>* pressingRecords;
/**
* A constant mask for NSEvent.modifierFlags that Flutter synchronizes with.
@ -396,7 +396,8 @@ void HandleResponse(bool handled, void* user_data);
* Its values are |responseId|s, and keys are the callback that was received
* along with the event.
*/
@property(nonatomic) NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>* pendingResponses;
@property(nonatomic, retain, readonly)
NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>* pendingResponses;
/**
* Compare the last modifier flags and the current, and dispatch synthesized

View File

@ -215,6 +215,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
[_registrars release];
_binaryMessenger.parent = nil;
[_binaryMessenger release];
[_isolateId release];
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
if (_flutterViewControllerWillDeallocObserver) {

View File

@ -7,8 +7,8 @@
@interface FlutterEngineGroup ()
@property(nonatomic, copy) NSString* name;
@property(nonatomic, strong) NSMutableArray<NSValue*>* engines;
@property(nonatomic, strong) FlutterDartProject* project;
@property(nonatomic, retain) NSMutableArray<NSValue*>* engines;
@property(nonatomic, retain) FlutterDartProject* project;
@end
@implementation FlutterEngineGroup {
@ -18,9 +18,9 @@
- (instancetype)initWithName:(NSString*)name project:(nullable FlutterDartProject*)project {
self = [super init];
if (self) {
self.name = name;
self.engines = [[NSMutableArray<NSValue*> alloc] init];
self.project = project;
_name = [name copy];
_engines = [[NSMutableArray<NSValue*> alloc] init];
_project = [project retain];
}
return self;
}
@ -30,6 +30,7 @@
[center removeObserver:self];
[_name release];
[_engines release];
[_project release];
[super dealloc];
}

View File

@ -42,4 +42,18 @@ FLUTTER_ASSERT_ARC
XCTAssertNotNil(spawnee);
}
- (void)testReleasesProjectOnDealloc {
__weak FlutterDartProject* weakProject;
@autoreleasepool {
FlutterDartProject* mockProject = OCMClassMock([FlutterDartProject class]);
FlutterEngineGroup* group = [[FlutterEngineGroup alloc] initWithName:@"foo"
project:mockProject];
weakProject = mockProject;
XCTAssertNotNil(weakProject);
group = nil;
mockProject = nil;
}
XCTAssertNil(weakProject);
}
@end

View File

@ -13,12 +13,14 @@ static constexpr CFTimeInterval kDistantFuture = 1.0e10;
/**
* The primary responders added by addPrimaryResponder.
*/
@property(nonatomic) NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
@property(nonatomic, retain, readonly)
NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
/**
* The secondary responders added by addSecondaryResponder.
*/
@property(nonatomic) NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders;
@property(nonatomic, retain, readonly)
NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders;
- (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
complete:(nonnull KeyEventCompleteCallback)callback
@ -88,7 +90,7 @@ static constexpr CFTimeInterval kDistantFuture = 1.0e10;
NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added.");
__block auto weakSelf = [self getWeakPtr];
__block int unreplied = [_primaryResponders count];
__block NSUInteger unreplied = [self.primaryResponders count];
__block BOOL anyHandled = false;
FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
unreplied--;

View File

@ -14,7 +14,7 @@
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@property(nonatomic, readonly) NSURL* url;
@property(nonatomic, retain, readonly) NSURL* url;
@end

View File

@ -206,8 +206,8 @@ static void DNSSD_API registrationCallback(DNSServiceRef sdRef,
// uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port
// number.
if (weak) {
NSURL* url =
[[NSURL alloc] initWithString:[NSString stringWithUTF8String:uri.c_str()]];
NSURL* url = [[[NSURL alloc]
initWithString:[NSString stringWithUTF8String:uri.c_str()]] autorelease];
weak.get().url = url;
if (weak.get().enableObservatoryPublication) {
[[weak.get() delegate] publishServiceProtocolPort:url];

View File

@ -84,7 +84,7 @@ void ResetAnchor(CALayer* layer) {
}
- (instancetype)initWithFrame:(CGRect)frame {
if ([super initWithFrame:frame]) {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = UIColor.clearColor;
}
return self;

View File

@ -650,7 +650,7 @@ static BOOL isScribbleAvailable() {
// currently only support UITextFields, and password saving only supports
// UITextFields and UITextViews, as of iOS 13.5.
@interface FlutterSecureTextInputView : FlutterTextInputView
@property(nonatomic, strong, readonly) UITextField* textField;
@property(nonatomic, retain, readonly) UITextField* textField;
@end
@implementation FlutterSecureTextInputView {
@ -693,7 +693,7 @@ static BOOL isScribbleAvailable() {
@property(nonatomic, assign) CGRect markedRect;
@property(nonatomic) BOOL isVisibleToAutofill;
@property(nonatomic, assign) BOOL accessibilityEnabled;
@property(nonatomic, strong) UITextInteraction* textInteraction API_AVAILABLE(ios(13.0));
@property(nonatomic, retain) UITextInteraction* textInteraction API_AVAILABLE(ios(13.0));
- (void)setEditableTransform:(NSArray*)matrix;
@end
@ -872,6 +872,9 @@ static BOOL isScribbleAvailable() {
[_autofillId release];
[_inputViewController release];
[_selectionRects release];
[_markedTextStyle release];
[_textContentType release];
[_textInteraction release];
[super dealloc];
}
@ -957,8 +960,8 @@ static BOOL isScribbleAvailable() {
}
- (NSRange)clampSelection:(NSRange)range forText:(NSString*)text {
int start = MIN(MAX(range.location, 0), text.length);
int length = MIN(range.length, text.length - start);
NSUInteger start = MIN(MAX(range.location, 0), text.length);
NSUInteger length = MIN(range.length, text.length - start);
return NSMakeRange(start, length);
}
@ -1118,8 +1121,8 @@ static BOOL isScribbleAvailable() {
NSRange textRange = ((FlutterTextRange*)range).range;
NSAssert(textRange.location != NSNotFound, @"Expected a valid text range.");
// Sanitize the range to prevent going out of bounds.
int location = MIN(textRange.location, self.text.length);
int length = MIN(self.text.length - location, textRange.length);
NSUInteger location = MIN(textRange.location, self.text.length);
NSUInteger length = MIN(self.text.length - location, textRange.length);
NSRange safeRange = NSMakeRange(location, length);
return [self.text substringWithRange:safeRange];
}
@ -1918,8 +1921,8 @@ static BOOL isScribbleAvailable() {
// The current password-autofillable input fields that have yet to be saved.
@property(nonatomic, readonly)
NSMutableDictionary<NSString*, FlutterTextInputView*>* autofillContext;
@property(nonatomic, strong) FlutterTextInputView* activeView;
@property(nonatomic, strong) FlutterTextInputViewAccessibilityHider* inputHider;
@property(nonatomic, retain) FlutterTextInputView* activeView;
@property(nonatomic, retain) FlutterTextInputViewAccessibilityHider* inputHider;
@property(nonatomic, readonly) id<FlutterViewResponder> viewResponder;
@end

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/93360
// The only point of this file is to ensure that the Flutter framework umbrella header can be
// cleanly imported from an Objective-C translation unit. The target that uses this file copies the
// headers to a path that simulates how users would actually import the framework outside of the

View File

@ -73,7 +73,7 @@ typedef struct MouseState {
* Keyboard animation properties
*/
@property(nonatomic, assign) double targetViewInsetBottom;
@property(nonatomic, strong) CADisplayLink* displayLink;
@property(nonatomic, retain) CADisplayLink* displayLink;
/**
* Creates and registers plugins used by this view controller.
@ -671,21 +671,24 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
}
- (void)addInternalPlugins {
[self.keyboardManager release];
self.keyboardManager = [[FlutterKeyboardManager alloc] init];
self.keyboardManager = [[[FlutterKeyboardManager alloc] init] autorelease];
fml::WeakPtr<FlutterViewController> weakSelf = [self getWeakPtr];
FlutterSendKeyEvent sendEvent =
^(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, void* userData) {
[_engine.get() sendKeyEvent:event callback:callback userData:userData];
[weakSelf.get()->_engine.get() sendKeyEvent:event callback:callback userData:userData];
};
[self.keyboardManager
addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc] initWithSendEvent:sendEvent]];
[self.keyboardManager addPrimaryResponder:[[FlutterChannelKeyResponder alloc]
initWithChannel:self.engine.keyEventChannel]];
[self.keyboardManager addSecondaryResponder:self.engine.textInputPlugin];
FlutterChannelKeyResponder* responder = [[[FlutterChannelKeyResponder alloc]
initWithChannel:self.engine.keyEventChannel] autorelease];
[self.keyboardManager addPrimaryResponder:responder];
FlutterTextInputPlugin* textInputPlugin = self.engine.textInputPlugin;
if (textInputPlugin != nil) {
[self.keyboardManager addSecondaryResponder:textInputPlugin];
}
}
- (void)removeInternalPlugins {
[self.keyboardManager release];
self.keyboardManager = nil;
}
@ -790,6 +793,8 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
[self removeInternalPlugins];
[self deregisterNotifications];
[_displayLink release];
[super dealloc];
}
@ -1733,6 +1738,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
NSData* restorationData = [[_engine.get() restorationPlugin] restorationData];
[coder encodeDataObject:restorationData];
[super encodeRestorableStateWithCoder:coder];
}
- (void)decodeRestorableStateWithCoder:(NSCoder*)coder {

View File

@ -760,6 +760,21 @@ typedef enum UIAccessibilityContrast : NSInteger {
[self waitForExpectations:@[ expectation ] timeout:1.0];
}
- (void)testReleasesKeyboardManagerOnDealloc {
__weak FlutterKeyboardManager* weakKeyboardManager = nil;
@autoreleasepool {
FlutterViewController* viewController = [[FlutterViewController alloc] init];
[viewController addInternalPlugins];
weakKeyboardManager = viewController.keyboardManager;
XCTAssertNotNil(weakKeyboardManager);
[viewController deregisterNotifications];
viewController = nil;
}
// View controller has released the keyboard manager.
XCTAssertNil(weakKeyboardManager);
}
- (void)testDoesntLoadViewInInit {
FlutterDartProject* project = [[FlutterDartProject alloc] init];
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];

View File

@ -31,7 +31,7 @@ extern NSNotificationName const FlutterViewControllerShowHomeIndicator;
@property(nonatomic, readonly) BOOL isPresentingViewController;
@property(nonatomic, readonly) BOOL isVoiceOverRunning;
@property(nonatomic) FlutterKeyboardManager* keyboardManager;
@property(nonatomic, retain) FlutterKeyboardManager* keyboardManager;
- (fml::WeakPtr<FlutterViewController>)getWeakPtr;
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController;
- (FlutterRestorationPlugin*)restorationPlugin;

View File

@ -146,7 +146,7 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) {
@end // FlutterSwitchSemanticsObject
@interface FlutterScrollableSemanticsObject ()
@property(nonatomic, strong) FlutterSemanticsScrollView* scrollView;
@property(nonatomic, retain) FlutterSemanticsScrollView* scrollView;
@end
@implementation FlutterScrollableSemanticsObject {
@ -409,7 +409,7 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) {
withAttributes:
(const flutter::StringAttributes&)attributes {
NSMutableAttributedString* attributedString =
[[NSMutableAttributedString alloc] initWithString:string];
[[[NSMutableAttributedString alloc] initWithString:string] autorelease];
for (const auto& attribute : attributes) {
NSRange range = NSMakeRange(attribute->start, attribute->end - attribute->start);
switch (attribute->type) {
@ -749,7 +749,7 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) {
@end
@interface FlutterPlatformViewSemanticsContainer ()
@property(nonatomic, strong) UIView* platformView;
@property(nonatomic, retain) UIView* platformView;
@end
@implementation FlutterPlatformViewSemanticsContainer

View File

@ -14,10 +14,10 @@
@interface FlutterInactiveTextInput : UIView <UITextInput>
@property(nonatomic, copy) NSString* text;
@property(nonatomic, readonly) NSMutableString* markedText;
@property(readwrite, copy) UITextRange* selectedTextRange;
@property(nonatomic, strong) UITextRange* markedTextRange;
@property(nonatomic, copy) NSDictionary* markedTextStyle;
@property(nonatomic, copy, readonly) NSMutableString* markedText;
@property(copy) UITextRange* selectedTextRange;
@property(nonatomic, strong, readonly) UITextRange* markedTextRange;
@property(nonatomic, copy) NSDictionary<NSAttributedStringKey, id>* markedTextStyle;
@property(nonatomic, assign) id<UITextInputDelegate> inputDelegate;
@end

View File

@ -16,8 +16,13 @@ static const UIAccessibilityTraits UIAccessibilityTraitUndocumentedEmptyLine = 0
@synthesize beginningOfDocument = _beginningOfDocument;
@synthesize endOfDocument = _endOfDocument;
- (instancetype)init {
return [super init];
- (void)dealloc {
[_text release];
[_markedText release];
[_markedTextRange release];
[_selectedTextRange release];
[_markedTextStyle release];
[super dealloc];
}
- (BOOL)hasText {

View File

@ -22,6 +22,7 @@ bool ShouldUseMetalRenderer() {
if (@available(iOS METAL_IOS_VERSION_BASELINE, *)) {
auto device = MTLCreateSystemDefaultDevice();
ios_version_supports_metal = [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v3];
[device release];
}
return ios_version_supports_metal;
}