From 5bd89dbb24866ef1162025166904021c5e1941c9 Mon Sep 17 00:00:00 2001 From: Jia Hao Goh Date: Wed, 1 Mar 2023 18:33:18 +0800 Subject: [PATCH 1/2] Optimize search for the default bundle --- .../framework/Source/FlutterDartProject.mm | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index f7d62e9c4cd8b..03e52fff4ff88 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -32,6 +32,37 @@ static const char* kApplicationKernelSnapshotFileName = "kernel_blob.bin"; +// Finds a bundle with the named `bundleID`. +// +// `+[NSBundle bundleWithIdentifier:]` is slow, and can take in the order of +// tens of milliseconds in a minimal flutter app, and closer to 100 milliseconds +// in a medium sized Flutter app. It is likely that the slowness comes from +// having to traverse and load all bundles known to the process. Using +// `+[NSBundle allframeworks]` and filtering also suffers from the same problem. +// +// This implementation is an optimization to limit the search space with a hint. +// Callers can provide a `hintURL` for where the bundle is expected to be +// located in. If the desired bundle cannot be found here, the implementation +// falls back to `+[NSBundle bundleWithIdentifier:]`. +static NSBundle* FLTBundleWithIdentifier(NSString* bundleID, NSURL* hintURL) { + NSArray* candidates = [[NSFileManager defaultManager] + contentsOfDirectoryAtURL:hintURL + includingPropertiesForKeys:@[] + options:0 + // Not interested in the error as there is a fallback. + error:nil]; + + for (NSURL* candidate in candidates) { + NSBundle* bundle = [NSBundle bundleWithURL:candidate]; + if ([bundle.bundleIdentifier isEqualToString:bundleID]) { + return bundle; + } + } + + // Fallback to slow implementation. + return [NSBundle bundleWithIdentifier:bundleID]; +} + flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) { auto command_line = flutter::CommandLineFromNSProcessInfo(); @@ -46,7 +77,11 @@ bool hasExplicitBundle = bundle != nil; if (bundle == nil) { - bundle = [NSBundle bundleWithIdentifier:[FlutterDartProject defaultBundleIdentifier]]; + // The default build system for Flutter places the default bundle in the + // same directory as the engine bundle (as they are both frameworks). + NSURL* defaultBundleHintURL = [engineBundle.bundleURL URLByDeletingLastPathComponent]; + bundle = + FLTBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier], defaultBundleHintURL); } if (bundle == nil) { bundle = mainBundle; From 32ac7cde08e14e790a32ca062212c4508ddd8943 Mon Sep 17 00:00:00 2001 From: Jia Hao Goh Date: Mon, 6 Mar 2023 13:13:06 +0800 Subject: [PATCH 2/2] Use privateFrameworksURL, tests --- .../framework/Source/FlutterDartProject.mm | 61 +++++++++++-------- .../Source/FlutterDartProjectTest.mm | 13 ++++ .../Source/FlutterDartProject_Internal.h | 2 + 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 03e52fff4ff88..e39758c0badfb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -32,33 +32,46 @@ static const char* kApplicationKernelSnapshotFileName = "kernel_blob.bin"; -// Finds a bundle with the named `bundleID`. -// -// `+[NSBundle bundleWithIdentifier:]` is slow, and can take in the order of -// tens of milliseconds in a minimal flutter app, and closer to 100 milliseconds -// in a medium sized Flutter app. It is likely that the slowness comes from -// having to traverse and load all bundles known to the process. Using -// `+[NSBundle allframeworks]` and filtering also suffers from the same problem. +// Finds a bundle with the named `bundleID` within `searchURL`. // -// This implementation is an optimization to limit the search space with a hint. -// Callers can provide a `hintURL` for where the bundle is expected to be -// located in. If the desired bundle cannot be found here, the implementation -// falls back to `+[NSBundle bundleWithIdentifier:]`. -static NSBundle* FLTBundleWithIdentifier(NSString* bundleID, NSURL* hintURL) { - NSArray* candidates = [[NSFileManager defaultManager] - contentsOfDirectoryAtURL:hintURL - includingPropertiesForKeys:@[] - options:0 - // Not interested in the error as there is a fallback. - error:nil]; - - for (NSURL* candidate in candidates) { +// Returns `nil` if the bundle cannot be found or if errors are encountered. +NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL) { + NSDirectoryEnumerator* frameworkEnumerator = [NSFileManager.defaultManager + enumeratorAtURL:searchURL + includingPropertiesForKeys:nil + options:NSDirectoryEnumerationSkipsSubdirectoryDescendants | + NSDirectoryEnumerationSkipsHiddenFiles + // Skip directories where errors are encountered. + errorHandler:nil]; + + for (NSURL* candidate in frameworkEnumerator) { NSBundle* bundle = [NSBundle bundleWithURL:candidate]; if ([bundle.bundleIdentifier isEqualToString:bundleID]) { return bundle; } } + return nil; +} +// Finds a bundle with the named `bundleID`. +// +// `+[NSBundle bundleWithIdentifier:]` is slow, and can take in the order of +// tens of milliseconds in a minimal flutter app, and closer to 100 milliseconds +// in a medium sized Flutter app on an iPhone 13. It is likely that the slowness +// comes from having to traverse and load all bundles known to the process. +// Using `+[NSBundle allframeworks]` and filtering also suffers from the same +// problem. +// +// This implementation is an optimization to first limit the search space to +// `+[NSBundle privateFrameworksURL]` of the main bundle, which is usually where +// frameworks used by this file are placed. If the desired bundle cannot be +// found here, the implementation falls back to +// `+[NSBundle bundleWithIdentifier:]`. +NS_INLINE NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID) { + NSBundle* bundle = FLTFrameworkBundleInternal(bundleID, NSBundle.mainBundle.privateFrameworksURL); + if (bundle != nil) { + return bundle; + } // Fallback to slow implementation. return [NSBundle bundleWithIdentifier:bundleID]; } @@ -77,11 +90,7 @@ bool hasExplicitBundle = bundle != nil; if (bundle == nil) { - // The default build system for Flutter places the default bundle in the - // same directory as the engine bundle (as they are both frameworks). - NSURL* defaultBundleHintURL = [engineBundle.bundleURL URLByDeletingLastPathComponent]; - bundle = - FLTBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier], defaultBundleHintURL); + bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]); } if (bundle == nil) { bundle = mainBundle; @@ -344,7 +353,7 @@ - (instancetype)initWithSettings:(const flutter::Settings&)settings { + (NSString*)flutterAssetsName:(NSBundle*)bundle { if (bundle == nil) { - bundle = [NSBundle bundleWithIdentifier:[FlutterDartProject defaultBundleIdentifier]]; + bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]); } if (bundle == nil) { bundle = [NSBundle mainBundle]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm index 3eb89543995fb..8de0007868b65 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm @@ -60,6 +60,19 @@ - (void)testLeakDartVMSettingsAreCorrectlyParsed { XCTAssertEqual(settings.leak_vm, NO); } +- (void)testFLTFrameworkBundleInternalWhenBundleIsNotPresent { + NSBundle* found = + FLTFrameworkBundleInternal(@"doesNotExist", NSBundle.mainBundle.privateFrameworksURL); + XCTAssertNil(found); +} + +- (void)testFLTFrameworkBundleInternalWhenBundleIsPresent { + NSString* presentBundleID = @"io.flutter.flutter"; + NSBundle* found = + FLTFrameworkBundleInternal(presentBundleID, NSBundle.mainBundle.privateFrameworksURL); + XCTAssertNotNil(found); +} + - (void)testEnableImpellerSettingIsCorrectlyParsed { // The FLTEnableImpeller's value is defined in Info.plist NSBundle* mainBundle = [NSBundle mainBundle]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h index c741a53478bab..50a42fe77f318 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN +NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL); + flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle = nil); @interface FlutterDartProject ()