Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ @implementation FlutterBinaryCodec
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
if (!_sharedInstance) {
_sharedInstance = [FlutterBinaryCodec new];
_sharedInstance = [[FlutterBinaryCodec alloc] init];
}
return _sharedInstance;
}
Expand All @@ -29,7 +29,7 @@ @implementation FlutterStringCodec
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
if (!_sharedInstance) {
_sharedInstance = [FlutterStringCodec new];
_sharedInstance = [[FlutterStringCodec alloc] init];
}
return _sharedInstance;
}
Expand All @@ -54,7 +54,7 @@ @implementation FlutterJSONMessageCodec
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
if (!_sharedInstance) {
_sharedInstance = [FlutterJSONMessageCodec new];
_sharedInstance = [[FlutterJSONMessageCodec alloc] init];
}
return _sharedInstance;
}
Expand Down Expand Up @@ -109,7 +109,7 @@ @implementation FlutterJSONMethodCodec
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
if (!_sharedInstance) {
_sharedInstance = [FlutterJSONMethodCodec new];
_sharedInstance = [[FlutterJSONMethodCodec alloc] init];
}
return _sharedInstance;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ FLUTTER_DARWIN_EXPORT
/**
* The name of the callback.
*/
@property(retain) NSString* callbackName;
@property(copy) NSString* callbackName;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Foundation objects with mutable variants (NSMutableString) should be copy.

/**
* 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

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ - (instancetype)init {
- (void)dealloc {
[_lifeCycleDelegate release];
[_rootFlutterViewControllerGetter release];
[_window release];
[super dealloc];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,22 @@ - (void)testLaunchUrlWithFragmentNoQueryParameter {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ + (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity {
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.
Expand All @@ -288,7 +288,7 @@ + (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,15 +349,15 @@ @interface FlutterEmbedderKeyResponder ()
*
* Set by the initializer.
*/
@property(nonatomic) FlutterSendKeyEvent sendEvent;
@property(nonatomic, copy, readonly) FlutterSendKeyEvent sendEvent;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/**
* A map of pressed keys.
*
* 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have readonly private properties?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't write this and I don't want to start a stylistic war, but I think properties should always preferred over ivars (and the backing ivar only touched in init, dealloc, getters, and setters), even when private, because of KVO, KVC, to enforce readonly, to enforce copy when it's readwrite.

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html

It’s best practice to use a property on an object any time you need to keep track of a value or another object.

Can we put a pin in this question for this PR? I only did the bare minimum to make the analyzer happy, plus some tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of the property vs ivar debate; I actually don't have a strong opinion on that. I was just curious about the addition of readonly to a private proprety. It just seems odd to say that code within a class can modify the collection, but not replace it with a different collection.

But this wasn't intended to be a PR-blocking question.

Copy link
Member Author

@jmagman jmagman Nov 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of the property vs ivar debate; I actually don't have a strong opinion on that.

Guess I'm always geared up for a religious war in these kinds of reviews 😉

Re: the readonly, you're right, that wasn't a static analyzer warning. I did it because the property wasn't being set other than in init, so it could be made readonly, which I did in the spirit of preferring stricter property attributes, readonly, immutable, nonnull, etc. But yeah, it's not API just private so it doesn't really matter (unless you're KVOing on the property and need to handle updating notifications in the property setter, which we aren't). I can change it back if you prefer.


/**
* A constant mask for NSEvent.modifierFlags that Flutter synchronizes with.
Expand Down Expand Up @@ -396,7 +396,8 @@ @interface FlutterEmbedderKeyResponder ()
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ - (void)dealloc {
[_registrars release];
_binaryMessenger.parent = nil;
[_binaryMessenger release];
[_isolateId release];

NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
if (_flutterViewControllerWillDeallocObserver) {
Expand Down
11 changes: 6 additions & 5 deletions shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -18,9 +18,9 @@ @implementation FlutterEngineGroup {
- (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;
}
Expand All @@ -30,6 +30,7 @@ - (void)dealloc {
[center removeObserver:self];
[_name release];
[_engines release];
[_project release];
[super dealloc];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,18 @@ - (void)testDeleteLastEngine {
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ @interface FlutterKeyboardManager ()
/**
* The primary responders added by addPrimaryResponder.
*/
@property(nonatomic) NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
@property(nonatomic, retain, readonly)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here.

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
Expand Down Expand Up @@ -84,7 +86,7 @@ - (void)handlePress:(nonnull FlutterUIPressProxy*)press
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--;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

@property(nonatomic, readonly) NSURL* url;
@property(nonatomic, retain, readonly) NSURL* url;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ - (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPubl
// 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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ @implementation FlutterClippingMaskView {
}

- (instancetype)initWithFrame:(CGRect)frame {
if ([super initWithFrame:frame]) {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = UIColor.clearColor;
}
return self;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ @implementation FlutterTextPlaceholder
// 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 {
Expand Down Expand Up @@ -693,7 +693,7 @@ @interface FlutterTextInputView ()
@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
Expand Down Expand Up @@ -872,6 +872,9 @@ - (void)dealloc {
[_autofillId release];
[_inputViewController release];
[_selectionRects release];
[_markedTextStyle release];
[_textContentType release];
[_textInteraction release];
[super dealloc];
}

Expand Down Expand Up @@ -957,8 +960,8 @@ - (NSRange)clampSelectionFromBase:(int)selectionBase
}

- (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);
}

Expand Down Expand Up @@ -1118,8 +1121,8 @@ - (NSString*)textInRange:(UITextRange*)range {
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];
}
Expand Down Expand Up @@ -1918,8 +1921,8 @@ @interface FlutterTextInputPlugin ()
// 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is new.
FlutterUmbrellaImport is used in the copy_and_verify_framework_module target, is not included in the engine framework source, and cannot be compiled on its own.

When clang-tidy is run on this file, it fails with a clang-diagnostic-error (compilation failure). Add a FLUTTER_NOLINT to this file with link to flutter/flutter#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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ @interface FlutterViewController () <FlutterBinaryMessenger, UIScrollViewDelegat
* 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.
Expand Down Expand Up @@ -671,21 +671,24 @@ - (void)viewDidLoad {
}

- (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];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This didn't show up in the linter, but this retain cycle was exposed when I wrote the testReleasesKeyboardManagerOnDealloc and the view controller wasn't being released.

};
[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) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.engine is nullable. When I tested it threw when adding nil to the secondary responder array.

[self.keyboardManager addSecondaryResponder:textInputPlugin];
}
}

- (void)removeInternalPlugins {
[self.keyboardManager release];
self.keyboardManager = nil;
}

Expand Down Expand Up @@ -786,6 +789,8 @@ - (void)deregisterNotifications {
- (void)dealloc {
[self removeInternalPlugins];
[self deregisterNotifications];

[_displayLink release];
[super dealloc];
}

Expand Down Expand Up @@ -1729,6 +1734,7 @@ - (void)scrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4))
- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
NSData* restorationData = [[_engine.get() restorationPlugin] restorationData];
[coder encodeDataObject:restorationData];
[super encodeRestorableStateWithCoder:coder];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an analyzer warning, encodeRestorableStateWithCoder needs to encode the super class's state.

}

- (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
Expand Down
Loading