diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index f7d62e9c4cd8b..e39758c0badfb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -32,6 +32,50 @@ static const char* kApplicationKernelSnapshotFileName = "kernel_blob.bin"; +// Finds a bundle with the named `bundleID` within `searchURL`. +// +// 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]; +} + flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) { auto command_line = flutter::CommandLineFromNSProcessInfo(); @@ -46,7 +90,7 @@ bool hasExplicitBundle = bundle != nil; if (bundle == nil) { - bundle = [NSBundle bundleWithIdentifier:[FlutterDartProject defaultBundleIdentifier]]; + bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]); } if (bundle == nil) { bundle = mainBundle; @@ -309,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 ()