The logic used by Flutter on IOS is very similar to that on Android. The App mainly provides initialization and View to FlutterEngine for rendering. The rest of Flutter is handled by FlutterEngine for subsequent work. Before FlutterUI can be loaded?

1. IOSApp package structure

2. How is the FlutterEngine library loaded

3.FlutterEngine initialization process

4. Start FlutterEngine

Ipa package structure

Ios package Flutter related resource file is stored in the Frameworks/App. The framework/flutter_assets Bundle, Engine related file is stored in the Frameworks/Flutter. The framework preserved the Flutter in two places related code, in the App structure, we see the Flutter actually relevant code are separated in the App, there is not much associated, FlutterEngine is integrated with the App code when compiled, so that the FlutterEngine can be loaded at startup. (Can we manually load the Flutter engine libraries so that they are more independent from ios apps?

flutter build ios --release
Copy the code

Run the command above and wait for the packaging tool to generate the ios files. The structure is as follows. The pictures are deleted in the middle to focus on the key points of analysis.

➜ Runner. App tree-l 3. ├─ appframeworkInfo.plist........ ├ ─ ─ Assets. Car ├ ─ ─ Base. Lproj │ ├ ─ ─ LaunchScreen. Storyboardc │ │ ├ ─ ─ j - lp 01 - oVM - view - Ze5-6 b - 2 t3. Nib │ │ ├ ─ ─ Info. The plist │ │ ├─ ├─ ├─ ├─ ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0. ├─ im0 └ ─ ─ UIViewController BYZ - 38 - t0r. Nib ├ ─ ─ the Debug. Xcconfig ├ ─ ─ App. The framework │ ├ ─ ─ App │ ├ ─ ─ the Info. The plist │ ├ ─ ─ _CodeSignature │ │ └ ─ ─ CodeResources │ └ ─ ─ flutter_assets │ ├ ─ ─ AssetManifest. Json │ ├ ─ ─ FontManifest. Json │ ├ ─ ─ LICENSE │ ├ ─ ─ fonts │ ├ ─ ─ isolate_snapshot_data │ ├ ─ ─ kernel_blob. Bin │ ├ ─ ─ packages │ └ ─ ─ vm_snapshot_data ├ ─ ─ Flutter. The framework │ ├ ─ ─ Flutter │ ├ ─ ─ the Info. The plist │ ├ ─ ─ _CodeSignature │ │ └ ─ ─ CodeResources │ └ ─ ─ icudtl. Dat ├ ─ ─ libswiftCore. Dylib ├ ─ ─ LibswiftCoreFoundation. Dylib ├ ─ ─ libswiftCoreGraphics. Dylib ├ ─ ─ libswiftDarwin. Dylib ├ ─ ─ libswiftDispatch. Dylib ├ ─ ─ LibswiftFoundation. Dylib └ ─ ─ libswiftObjectiveC. Dylib ├ ─ ─ Info. The plist ├ ─ ─ PkgInfo ├ ─ ─ Runner ├ ─ ─ _CodeSignature │ └ ─ ─ CodeResources └ ─ ─ embedded. MobileprovisionCopy the code

How is the FlutterEngine library loaded

Configure the Xcode development environment

Write Link Map File XCode -> Project -> Build Settings -> Search Map -> Set Write Link Map File to YES, Note: Please restore to NO flutteriosconfig1.png before package release

Configuring this option allows Xcode to be packaged as a Flutter library in the Framework directory,

Note: There are no dynamic libraries set up in Linked Settings, which are opened via dlopen. Dynamic Libraries that are set in Link Framwokrs and Libraries will be loaded when the application starts.

Think of the dynamic library as a separate executable with no entry to the main function, and copy it directly to the Frameworks directory in the.app directory in the iOS package. Since it is an executable, the internal compilation of the connection process is complete, and the connection to be processed is automatically loaded + link by the operating system’s dyLD at load time.

flutteriosconfigure2.png

After compiling, go to the compilation directory to find the TXT File, the File name and Path is the above Path to Link Map File located in

This LinkMap shows the whole picture of the executable file, listing the information of each.o object file after compilation (including the static link library. A), as well as the code segment of each object file, data segment storage details.

LinkMap structure

1. List the target files first (file numbers in parentheses) :

➜ Runner. Build cat Runner -linkmap-normal-arm64.txt # Path: /Users/cuco/Desktop/flutter_app/build/ios/Debug-iphoneos/Runner.app/Runner # Arch: arm64 # Object files: [ 0] linker synthesized [ 1] /Users/cuco/Desktop/flutter_app/build/ios/Runner.build/Debug-iphoneos/Runner.build/Objects-normal/arm64/GeneratedPluginR egistrant.o [ 2] /Users/cuco/Desktop/flutter_app/build/ios/Runner.build/Debug-iphoneos/Runner.build/Objects-normal/arm64/AppDelegate.o [ 3] /Users/cuco/Desktop/flutter_app/build/ios/Runner.build/Debug-iphoneos/Runner.build/Objects-normal/arm64/Runner_vers.o [4] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a(arclite .o) [ 5] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/System/Library/Fr ameworks//Foundation.framework/Foundation.tbd [ 6] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/libobjc. T bd [ 7] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/libSystem .tbd [ 8] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/System/Library/Fr ameworks//CoreFoundation.framework/CoreFoundation.tbd [ 9] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/libswiftCompatibil ityDynamicReplacements.a(DynamicReplaceable.cpp.o) [ 10] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/libswiftCompatibil ity50.a(Overrides.cpp.o) [ 11] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/System/Library/Fr ameworks//UIKit.framework/UIKit.tbd [ 12] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/swift/lib swiftObjectiveC.tbd [ 13] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/swift/lib swiftFoundation.tbd [ 14] / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneOS platform/Developer/SDKs/iPhoneOS13.0 SDK/usr/lib/swift/lib swiftCore.tbdCopy the code

2. This is followed by a segment table that describes the offset position and size of each segment in the final compiled executable, including the code segment (__TEXT, which holds the compiled machine code of the program code segment) and the data segment (__DATA, which holds variable values).

The first column is the offset position of the data in the file, the second column is the size of the segment, the third column is the segment type, code segment and data segment, and the fourth column is the segment name. For example, the address 0x10304FD9C of __stubs on the second line is the address 0x100005B00 plus the size 0x0304A29C of __text on the first line. Here you can clearly see the proportion of various types of data in the final executable. For example, __text represents compiled program execution statements, __data represents initialized global and local static variables, __bSS represents uninitialized global and local static variables, __cString represents string constants in the code, And so on.

  # Sections:
  # Address	Size    	Segment	Section
  0x10000473C	0x000028F0	__TEXT	__text
  0x10000702C	0x0000036C	__TEXT	__stubs
  0x100007398	0x00000384	__TEXT	__stub_helper
  0x10000771C	0x00000043	__TEXT	__objc_classname
  0x10000775F	0x000001C8	__TEXT	__objc_methname
  0x100007927	0x00000028	__TEXT	__objc_methtype
  0x100007950	0x00000210	__TEXT	__cstring
  0x100007B60	0x0000026F	__TEXT	__const
  0x100007DD0	0x0000008E	__TEXT	__swift5_typeref
  0x100007E60	0x0000002C	__TEXT	__swift5_fieldmd
  0x100007E8C	0x00000014	__TEXT	__swift5_builtin
  0x100007EA0	0x00000023	__TEXT	__swift5_reflstr
  0x100007EC4	0x00000030	__TEXT	__swift5_assocty
  0x100007EF4	0x00000018	__TEXT	__swift5_proto
  0x100007F0C	0x00000008	__TEXT	__swift5_types
  0x100007F14	0x000000E4	__TEXT	__unwind_info
  0x100008000	0x000000A0	__DATA	__got
  0x1000080A0	0x00000248	__DATA	__la_symbol_ptr
  0x1000082E8	0x000000E0	__DATA	__const
  0x1000083C8	0x00000010	__DATA	__objc_classlist
  0x1000083D8	0x00000008	__DATA	__objc_nlclslist
  0x1000083E0	0x00000008	__DATA	__objc_protolist
  0x1000083E8	0x00000008	__DATA	__objc_imageinfo
  0x1000083F0	0x00000270	__DATA	__objc_const
  0x100008660	0x000000B8	__DATA	__objc_selrefs
  0x100008718	0x00000008	__DATA	__objc_protorefs
  0x100008720	0x00000008	__DATA	__objc_classrefs
  0x100008728	0x00000100	__DATA	__objc_data
  0x100008828	0x00000115	__DATA	__data
  0x100008940	0x000000B8	__DATA	__swift_hooks
  0x100008A00	0x000004A0	__DATA	__bss
Copy the code

3. Then list the location and space occupied by each corresponding field according to each file in the order of the above table

Similarly, the first column is the offset address of the data in the file, the second column is the occupied size, the third column is the number of the file, corresponding to the above Object Files list, and the last column is the name.

  # Symbols:
  # Address	Size    	File  Name
  0x10000473C	0x00000040	[  1] +[GeneratedPluginRegistrant registerWithRegistry:]
  0x10000477C	0x00000204	[  2] _$s6Runner11AppDelegateC11application_29didFinishLaunchingWithOptionsSbSo13UIApplicationC_SDySo0j6LaunchI3KeyaypGSgtF
  0x100004980	0x00000064	[  2] _$s6Runner11AppDelegateCMa
  0x1000049E4	0x00000094	[  2] _$sSo29UIApplicationLaunchOptionsKeyaMa
  0x100004A78	0x00000070	[  2] _$sSo29UIApplicationLaunchOptionsKeyaABSHSCWl
  0x100004AE8	0x0000012C	[  2] _$s6Runner11AppDelegateC11application_29didFinishLaunchingWithOptionsSbSo13UIApplicationC_SDySo0j6LaunchI3KeyaypGSgtFTo
  0x100004C14	0x00000030	[  2] _$s6Runner11AppDelegateCACycfC
  0x100004C44	0x00000098	[  2] _$s6Runner11AppDelegateCACycfc
  0x100004CDC	0x0000002C	[  2] _$s6Runner11AppDelegateCACycfcTo
  0x100004D08	0x00000070	[  2] _$s6Runner11AppDelegateCfD
  0x100004D78	0x00000070	[  2] _main
  0x100004DE8	0x00000058	[  2] _$sSo29UIApplicationLaunchOptionsKeya8rawValueSSvg
  0x100004E40	0x00000070	[  2] _$sSo29UIApplicationLaunchOptionsKeya8rawValueABSS_tcfC
Copy the code

.

4. Obsolete & redundant duplicate fields

# Dead Stripped Symbols: # Size File Name <<dead>> 0x00000016 [ 2] literal string: registerWithRegistry: <<dead>> 0x00000005 [ 4] literal string: init <<dead>> 0x00000058 [ 9] _swift_getFunctionReplacement50 <<dead>> 0x00000048 [ 9] _swift_getOrigOfReplaceable50 <<dead>> 0x00000007 [ 0] literal string: __TEXT <<dead>> 0x00000000 [ 7] _pthread_getspecific <<dead>> 0x00000000 [ 7] _pthread_setspecific <<dead>> 0x00000000 [  14] _swift_getFunctionReplacement <<dead>> 0x00000000 [ 14] _swift_getOrigOfReplaceaCopy the code

Through the configuration of the above two steps, we can see the intermediate files of executable files that can be produced in the generated Runner. App. Here, we analyze the structure of The whole Flutter in detail and just find the loading entry of the Flutter. How does the whole Flutter framework work on Ios? Are we focusing on a particular point

Load process of Flutter library (Analysis of Mach-O files)

How to enter the normal startup process of IOSApp? There are too many analysis about the startup process on the website. It will be loaded when app starts. Will call in the AppDelegate didFinishLaunchingWithOptions parameters, the method of logical initialization began to Flutter, other App initialization logic has not changed.

Initialize FlutterAppDelegate

As soon as the App is launched, it starts to initialize the developer’s code in the AppDelegate class, AppDelegate inherited FlutterAppDelegate then we can start initialization, FlutterEngine IOS related source on flutter/shell/platform/Darwin/IOS directory

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
Copy the code

FlutterAppDelegate realized in Engine Source directory/Engine/SRC/flutter/shell/platform/Darwin/ios/framework/Source/FlutterAppDelegate. Mm, FlutterAppDelegate inheritance:

FlutterUI is directly displayed on a UIView (currently all flutterUi-related logic is displayed on this UIView). IOS clearly needs to distribute related events to FlutterUI for processing when FlutterUI is displayed. The direct communication between FlutterUI and App is naturally through the FlutterPlugin, which includes system plug-in and user-defined Channel. Meanwhile, all operations of FlutterEngine still need to be synchronized with App. If the App falls back into the background, FlutterEngine needs to handle its own life cycle.

FLUTTER_EXPORT
@interface FlutterAppDelegate
    : UIResponder <UIApplicationDelegate, FlutterPluginRegistry, FlutterAppLifeCycleProvider>
Copy the code

FlutterAppDelegate implementation class/engine/SRC/flutter/shell/platform/Darwin/ios/framework/Source/FlutterAppDelegate. Mm, Init is called when the FlutterAppDelegate is instantiated. The parent method is called first. If the initialization succeeds, The initialization FlutterPluginAppLifeCycleDelegate FlutterEngine and app a lifecycle, and registered plug-in management. In the initialization method of FlutterPluginAppLifeCycleDelegate initialization Flutter in the init file directory lookups

- (instancetype)init {
  if (self = [super init]) {
    _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
  }
  return self;
}
Copy the code

FlutterPluginAppLifeCycleDelegate

In/Users/cuco/engine/SRC/flutter/shell/platform/Darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate mm initialization is to call ini T instance method to initialize the flutter. During the initialization process, the cache directory is obtained. The image files and data files of flutter have been configured and the path has been set.

/// Cache directory
static const char* kCallbackCacheSubDir = "Library/Caches/";
- (instancetype)init {
  if (self = [super init]) {
    std: :string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
    [FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
    _pluginDelegates = [[NSPointerArray weakObjectsPointerArray] retain];
  }
  return self;
}
Copy the code

FlutterCallbackCache

DartCallbackCache file path related categories: flutter/lib/UI/plugins/callback_cache. H is the following code to load a file directory initialization operation, the basic knowledge

+ (void)setCachePath:(NSString*)path { assert(path ! = nil); blink::DartCallbackCache::SetCachePath([path UTF8String]); NSString* cache_path = [NSString stringWithUTF8String:blink::DartCallbackCache::GetCachePath().c_str()];// Set the "Do Not Backup" flag to ensure that the cache isn't moved off disk in
  // low-memory situations.
  if(! [[NSFileManager defaultManager] fileExistsAtPath:cache_path]) { [[NSFileManager defaultManager] createFileAtPath:cache_path contents:nil attributes:nil]; NSError* error = nil; NSURL* URL = [NSURL fileURLWithPath:cache_path]; BOOL success = [URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];if(! success) { NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
    }
  }
}
@end

Copy the code

The FlutterPluginRegistry plug-in initialization process

The implementation code of FlutterPluginRegistry is implemented in FlutterAppDelegate. Mm. Please refer to the code for the specific implementation process

- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
  UIViewController* rootViewController = _window.rootViewController;
  if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
    return
        [[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey];
  }
  return nil;
}

- (BOOL)hasPlugin:(NSString*)pluginKey {
  UIViewController* rootViewController = _window.rootViewController;
  if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
    return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey];
  }
  return false;
}

- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
  UIViewController* rootViewController = _window.rootViewController;
  if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
    return [[(FlutterViewController*)rootViewController pluginRegistry]
        valuePublishedByPlugin:pluginKey];
  }
  return nil;
}
Copy the code

After the above loading, FlutterEngine’s core code is initialized. The FlutterAppDelegate initialization process mainly does:

1. Load related plug-in information

2. Initialize the cache directory

3. Bind the life cycle of the FlutterEngine to the App

4. Call FlutterEngine for communication in different App life cycles

It’s basically doing initialization of global information

FlutterViewController initialization process

The initialization process of FlutterEngine is mainly to bind UI events in FlutterViewController and initialize logic in related life cycle. We first analyze key points and then share each point in detail in subsequent articles. All operations are invoked in the upper cycle method of the FlutterViewController

Flutter/shell/platform/Darwin/ios/framework/Source/FlutterEngine mm mainly implements the App layer and FlutterEngine an entry control logic

Flutter/shell/platform/Darwin/ios/framework/Source/FlutterView mm inheritance UIView provides a FlutterEngine engine for drawing operation

NotificationCenterFlutterEngine interact and App notification channel

1. - init: initialize FlutterEngine, FlutterView, setupNotificationCenterObservers register 2. AwakeFromNib: 3. LoadView: 4. ViewDidLoad: 5. ViewWillAppear: ViewWillLayoutSubviews: 8.viewDidLayOutSubViews: ViewDidAppear: Update Local information, update user Settings, update access status 10. ViewWillDisappear: 11. ViewDidDisappear: Update the associated FlutterUICopy the code

When the iosAPP is started, there is not much to do, mainly initialize the path of loading data, listen to the iosAPP life cycle, and register some event callback logic, then the App has been initialized, The FlutterViewController is then initialized, and when the FlutterViewController is initialized, The init method Flutter/shell/platform/Darwin/ios/framework/Source/FlutterViewController. Mm, in the init FlutterViewController initialization is called initialization function

- (instancetype)init {
  return [self initWithProject:nil nibName:nil bundle:nil];
}

Copy the code

FlutterViewController initialization entry

1. Initialize the entire FlutterEngine framework initWithProject, save the FlutterViewController to a weak reference, initialize the FlutterEngine, and call the initWithName method for initialization

2. Initializing FlutterView is mainly the interface of IOSUI interaction, and mainly functions as a management logic class of FlutterEngine and IOSUI, which is provided to FlutterEngine for rendering processing

3. Start SplashScreenView

  1. CreateShell: FlutterEngine and uniform interface platform directly

6.performCommonViewControllerInitialization

7.setupNotificationCenterObservers

- (instancetype)initWithProject:(FlutterDartProject*)projectOrNil
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  if (self) {
    _viewOpaque = YES;
    _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
    _engine.reset([[FlutterEngine alloc] initWithName:@"io.flutter"
                                              project:projectOrNil
                               allowHeadlessExecution:NO]);
    _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
    [_engine.get() createShell:nil libraryURI:nil];
    _engineNeedsLaunch = YES;
    [self loadDefaultSplashScreenView];
    [self performCommonViewControllerInitialization];
  }

  return self;
}
Copy the code

FlutterEngine

In FlutterEngine initialization, the relevant resource recruitment is mainly found, and the initialization operation is carried out by four threads and message queues. The initialization is mainly carried out inside FlutterEngine, and there is no business code logic being loaded

1.FlutterDartProject: initializes flutter_asset-related file path resolution during the entire FlutterEngine initialization process, parses command line parameters, and builds default process parameters

2. FlutterPlatformViewsController: the main function is to handle FlutterEngine side View logic flutter/shell/platform/Darwin/ios/framework/Source/Flut terPlatformViews.mm

3.FlutterView

3.setupChannels


- (instancetype)initWithName:(NSString*)labelPrefix
                     project:(FlutterDartProject*)projectOrNil
      allowHeadlessExecution:(BOOL)allowHeadlessExecution {
  self = [super init];
  NSAssert(self, @"Super init cannot be nil");
  NSAssert(labelPrefix, @"labelPrefix is required");

  _allowHeadlessExecution = allowHeadlessExecution;
  _labelPrefix = [labelPrefix copy];

  _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(self);

  if (projectOrNil == nil)
    _dartProject.reset([[FlutterDartProject alloc] init]);
  else
    _dartProject.reset([projectOrNil retain]);

  _pluginPublications = [NSMutableDictionary new];
  _platformViewsController.reset(new shell::FlutterPlatformViewsController());

  [self setupChannels];

  return self;
}
Copy the code

FlutterDartProject

- (instancetype)initWithPrecompiledDartBundle:(NSBundle*)bundle {
  self = [super init];

  if (self) {
    _precompiledDartBundle.reset([bundle retain]);
    _settings = DefaultSettingsForProcess(bundle);
  }

  returnself; } to find the path of the Frameworks/App. Framework/flutter_assets + (nsstrings *) flutterAssetsName: (NSBundle *) bundle {nsstrings * flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"];
  if (flutterAssetsName == nil) {
    flutterAssetsName = @"Frameworks/App.framework/flutter_assets";
  }
  return flutterAssetsName;
}

Copy the code

DefaultSettingsForProcess

Set Flutter_asset load path, set FlutterEngine how to find executable file path during these processes. In the package structure of App above, we can see that Flutter related resource files, then when FlutterEngine starts, We can then load the associated Flutter code and the associated resource file path

static blink::Settings DefaultSettingsForProcess(NSBundle* bundle = nil) {
  auto command_line = shell::CommandLineFromNSProcessInfo();

  // Precedence:
  // 1. Settings from the specified NSBundle.
  // 2. Settings passed explicitly via command-line arguments.
  // 3. Settings from the NSBundle with the default bundle ID.
  // 4. Settings from the main NSBundle and default values.

  NSBundle* mainBundle = [NSBundle mainBundle];
  NSBundle* engineBundle = [NSBundle bundleForClass:[FlutterViewController class]];

  boolhasExplicitBundle = bundle ! = nil;if (bundle == nil) {
    bundle = [NSBundle bundleWithIdentifier:[FlutterDartProject defaultBundleIdentifier]];
  }
  if (bundle == nil) {
    bundle = mainBundle;
  }

  auto settings = shell::SettingsFromCommandLine(command_line);

  settings.task_observer_add = [](intptr_t key, fml::closure callback) {
    fml::MessageLoop::GetCurrent().AddTaskObserver(key, std::move(callback));
  };

  settings.task_observer_remove = [](intptr_t key) {
    fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
  };

  // The command line arguments may not always be complete. If they aren't, attempt to fill in
  // defaults.

  // Flutter ships the ICU data file in the the bundle of the engine. Look for it there.
  if (settings.icu_data_path.size() == 0) {
    NSString* icuDataPath = [engineBundle pathForResource:@"icudtl" ofType:@"dat"];
    if (icuDataPath.length > 0) { settings.icu_data_path = icuDataPath.UTF8String; }}if (blink::DartVM::IsRunningPrecompiledCode()) {
    if (hasExplicitBundle) {
      NSString* executablePath = bundle.executablePath;
      if([[NSFileManager defaultManager] fileExistsAtPath:executablePath]) { settings.application_library_path = executablePath.UTF8String; }}// No application bundle specified. Try a known location from the main bundle's Info.plist.
    if (settings.application_library_path.size() == 0) {
      NSString* libraryName = [mainBundle objectForInfoDictionaryKey:@"FLTLibraryPath"];
      NSString* libraryPath = [mainBundle pathForResource:libraryName ofType:@""];
      if (libraryPath.length > 0) {
        NSString* executablePath = [NSBundle bundleWithPath:libraryPath].executablePath;
        if (executablePath.length > 0) { settings.application_library_path = executablePath.UTF8String; }}}// In case the application bundle is still not specified, look for the App.framework in the
    // Frameworks directory.
    if (settings.application_library_path.size() == 0) {
      NSString* applicationFrameworkPath = [mainBundle pathForResource:@"Frameworks/App.framework"
                                                                ofType:@""];
      if (applicationFrameworkPath.length > 0) {
        NSString* executablePath =
            [NSBundle bundleWithPath:applicationFrameworkPath].executablePath;
        if (executablePath.length > 0) { settings.application_library_path = executablePath.UTF8String; }}}}// Checks to see if the flutter assets directory is already present.
  if (settings.assets_path.size() == 0) {
    NSString* assetsName = [FlutterDartProject flutterAssetsName:bundle];
    NSString* assetsPath = [bundle pathForResource:assetsName ofType:@""];

    if (assetsPath.length == 0) {
      assetsPath = [mainBundle pathForResource:assetsName ofType:@""];
    }

    if (assetsPath.length == 0) {
      NSLog(@"Failed to find assets path for \"%@\"", assetsName);
    } else {
      settings.assets_path = assetsPath.UTF8String;

      // Check if there is an application kernel snapshot in the assets directory we could
      // potentially use. Looking for the snapshot makes sense only if we have a VM that can use
      // it.
      if(! blink::DartVM::IsRunningPrecompiledCode()) { NSURL* applicationKernelSnapshotURL = [NSURL URLWithString:@(kApplicationKernelSnapshotFileName) relativeToURL:[NSURL fileURLWithPath:assetsPath]];if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) {
          settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String;
        } else {
          NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path); }}}}#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
  // There are no ownership concerns here as all mappings are owned by the
  // embedder and not the engine.
  auto make_mapping_callback = [](const uint8_t* mapping, size_t size) {
    return [mapping, size]() { return std::make_unique<fml::NonOwnedMapping>(mapping, size); };
  };

  settings.dart_library_sources_kernel =
      make_mapping_callback(kPlatformStrongDill, kPlatformStrongDillSize);
#endif  // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG

  return settings;
}

Copy the code

setupNotificationCenterObservers

- (void)setupNotificationCenterObservers { NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(onOrientationPreferencesUpdated:) name:@(shell::kOrientationUpdateNotificationName) object:nil]; [center addObserver:self selector:@selector(onPreferredStatusBarStyleUpdated:) name:@(shell::kOverlayStyleUpdateNotificationName) object:nil]; [center addObserver:self selector:@selector(applicationBecameActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; [center addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification  object:nil]; [center addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [center addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; [center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; [center addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil]; [center addObserver:self selector:@selector(onLocaleUpdated:) name:NSCurrentLocaleDidChangeNotification object:nil]; [center addObserver:self selector:@selector(onAccessibilityStatusChanged:) name:UIAccessibilityVoiceOverStatusChanged object:nil]; [center addObserver:self selector:@selector(onAccessibilityStatusChanged:) name:UIAccessibilitySwitchControlStatusDidChangeNotification object:nil]; [center addObserver:self selector:@selector(onAccessibilityStatusChanged:) name:UIAccessibilitySpeakScreenStatusDidChangeNotification object:nil]; [center addObserver:self selector:@selector(onAccessibilityStatusChanged:) name:UIAccessibilityInvertColorsStatusDidChangeNotification object:nil]; [center addObserver:self selector:@selector(onAccessibilityStatusChanged:) name:UIAccessibilityReduceMotionStatusDidChangeNotification object:nil]; [center addObserver:self selector:@selector(onAccessibilityStatusChanged:) name:UIAccessibilityBoldTextStatusDidChangeNotification object:nil]; [center addObserver:self selector:@selector(onMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [center addObserver:self selector:@selector(onUserSettingsChanged:) name:UIContentSizeCategoryDidChangeNotification object:nil]; }Copy the code

setupChannels

Engine/SRC/flutter/shell/platform/Darwin/ios/framework/Source/FlutterEngine. Registered in mm FlutterUI layer and ios Plugin, between these plugins is a system level

- (void)setupChannels {
  _localizationChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/localization"
      binaryMessenger:self
                codec:[FlutterJSONMethodCodec sharedInstance]]);

  _navigationChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/navigation"
      binaryMessenger:self
                codec:[FlutterJSONMethodCodec sharedInstance]]);

  _platformChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/platform"
      binaryMessenger:self
                codec:[FlutterJSONMethodCodec sharedInstance]]);

  _platformViewsChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/platform_views"
      binaryMessenger:self
                codec:[FlutterStandardMethodCodec sharedInstance]]);

  _textInputChannel.reset([[FlutterMethodChannel alloc]
         initWithName:@"flutter/textinput"
      binaryMessenger:self
                codec:[FlutterJSONMethodCodec sharedInstance]]);

  _lifecycleChannel.reset([[FlutterBasicMessageChannel alloc]
         initWithName:@"flutter/lifecycle"
      binaryMessenger:self
                codec:[FlutterStringCodec sharedInstance]]);

  _systemChannel.reset([[FlutterBasicMessageChannel alloc]
         initWithName:@"flutter/system"
      binaryMessenger:self
                codec:[FlutterJSONMessageCodec sharedInstance]]);

  _settingsChannel.reset([[FlutterBasicMessageChannel alloc]
         initWithName:@"flutter/settings"
      binaryMessenger:self
                codec:[FlutterJSONMessageCodec sharedInstance]]);

  _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
  _textInputPlugin.get().textInputDelegate = self;

  _platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]);
}
Copy the code

FlutterViewcontroller is bound to the flutterEngine.mm instance

- (void)setViewController:(FlutterViewController*)viewController {
  FML_DCHECK(self.iosPlatformView);
  _viewController = [viewController getWeakPtr];
  self.iosPlatformView->SetOwnerViewController(_viewController);
  [self maybeSetupPlatformViewChannels];
}
Copy the code

FlutterView

Then return to flutter/shell/platform/Darwin/ios/framework/Source/FlutterViewController mm initWithProject continue to analysis, initialize flutter/shell/p Latform Darwin/ios/framework/Source/FlutterView. Mm said initial Frame

- (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate opaque:(BOOL)opaque {
  FML_DCHECK(delegate) << "Delegate must not be nil.";
  self = [super initWithFrame:CGRectNull];

  if (self) {
    _delegate = delegate;
    self.layer.opaque = opaque;
  }

  return self;
}
Copy the code

FlutterEngine: createShell

This method is described in detail in the Android startup process and is not explained in more detail here

1. Initialize the main() function of the FlutterDart layer as the entry point

2. Create Rasterizer

3. Start message queue MessageLoop

4. Create platform, GPU, UI, and I/O

- (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { if (_shell ! = nullptr) { FML_LOG(WARNING) << "This FlutterEngine was already invoked."; return NO; } static size_t shellCount = 1; auto settings = [_dartProject.get() settings]; if (libraryURI) { FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; settings.advisory_script_entrypoint = entrypoint.UTF8String; settings.advisory_script_uri = libraryURI.UTF8String; } else if (entrypoint) { settings.advisory_script_entrypoint = entrypoint.UTF8String; settings.advisory_script_uri = std::string("main.dart"); } else { settings.advisory_script_entrypoint = std::string("main"); settings.advisory_script_uri = std::string("main.dart"); } const auto threadLabel = [NSString stringWithFormat:@"%@.%zu", _labelPrefix, shellCount++]; FML_DLOG(INFO) << "Creating threadHost for " << threadLabel.UTF8String; // The current thread will be used as the platform thread. Ensure that the message loop is // initialized. fml::MessageLoop::EnsureInitializedForCurrentThread(); _threadHost = { threadLabel.UTF8String, // label shell::ThreadHost::Type::UI | shell::ThreadHost::Type::GPU | shell::ThreadHost::Type::IO}; // Lambda captures by pointers to ObjC objects are fine here because the // create call is // synchronous. shell::Shell::CreateCallback<shell::PlatformView> on_create_platform_view = [](shell::Shell& shell) { return std::make_unique<shell::PlatformViewIOS>(shell, shell.GetTaskRunners()); }; shell::Shell::CreateCallback<shell::Rasterizer> on_create_rasterizer = [](shell::Shell& shell) { return std::make_unique<shell::Rasterizer>(shell.GetTaskRunners()); }; if (shell::IsIosEmbeddedViewsPreviewEnabled()) { // Embedded views requires the gpu and the platform views to be the same. // The plan is to eventually dynamically merge the threads when there's a // platform view in the layer tree. // For now we run in a single threaded configuration. // TODO(amirh/chinmaygarde): merge only the gpu and platform threads. // https://github.com/flutter/flutter/issues/23974 // TODO(amirh/chinmaygarde):  remove this, and dynamically change the thread configuration. // https://github.com/flutter/flutter/issues/23975 blink::TaskRunners task_runners(threadLabel.UTF8String, // label fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform fml::MessageLoop::GetCurrent().GetTaskRunner(), // gpu fml::MessageLoop::GetCurrent().GetTaskRunner(), // ui fml::MessageLoop::GetCurrent().GetTaskRunner() // io ); // Create the shell. This is a blocking operation. _shell = shell::Shell::Create(std::move(task_runners), // task runners std::move(settings), // settings on_create_platform_view, // platform view creation on_create_rasterizer // rasterzier creation ); } else { blink::TaskRunners task_runners(threadLabel.UTF8String, // label fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform _threadHost.gpu_thread->GetTaskRunner(), // gpu _threadHost.ui_thread->GetTaskRunner(), // ui _threadHost.io_thread->GetTaskRunner() // io ); // Create the shell. This is a blocking operation. _shell = shell::Shell::Create(std::move(task_runners), // task runners std::move(settings), // settings on_create_platform_view, // platform view creation on_create_rasterizer // rasterzier creation ); } if (_shell == nullptr) { FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: " << entrypoint.UTF8String; } else { [self setupChannels]; if (! _platformViewsController) { _platformViewsController.reset(new shell::FlutterPlatformViewsController()); } _publisher.reset([[FlutterObservatoryPublisher alloc] init]); [self maybeSetupPlatformViewChannels]; } return _shell ! = nullptr; }Copy the code

In the previous call, we were most concerned with the Shell creation process. Flutter/shell/common/shell. Cc of this class is all platforms and FlutterPlatformView public entry point, after the call the following function, CreateShellOnPlatformThread create flutter related operating environment

// Create the shell. This is a blocking operation.
_shell = shell::Shell::Create(std::move(task_runners),  // task runners
                              std::move(settings),      // settings
                              on_create_platform_view,  // platform view creation
                              on_create_rasterizer      // rasterzier creation
);
Copy the code

The above part is initialized on the IOS side, and when the shell:: shell:: Create method is called, it truly enters the processing logic that is unified across all platforms of FlutterEngine

loadView

In the life cycle function of FlutterViewController, FlutterView was bound to the current FlutterViewController initialization operation View for display, and the SplashView initializing App startup was initialized for initialization. Several methods are provided for loading splashViews with different type definitions

- (void)loadView {
  self.view = _flutterView.get();
  self.view.multipleTouchEnabled = YES;
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

  [self installSplashScreenViewIfNecessary];
}
Copy the code

FlutterEngine:launchEngine

Call FlutterEngine in the viewWillAppear lifecycle function for initialization

- (void)viewWillAppear:(BOOL)animated {
  TRACE_EVENT0("flutter"."viewWillAppear");

  if (_engineNeedsLaunch) {
    [_engine.get() launchEngine:nil libraryURI:nil];
    [_engine.get() setViewController:self];
    _engineNeedsLaunch = NO;
  }

  // Only recreate surface on subsequent appearances when viewport metrics are known.
  // First time surface creation is done on viewDidLayoutSubviews.
  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
  [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];

  [super viewWillAppear:animated];
}
Copy the code

FlutterEngine:Run

1. Load the configuration and find the entry point of the specified FlutterEngine, that is, the entry point of the startup function of the FlutterUI layer

2. Call FltuterEngine – > Run method start FlutterEngine loaded FltuterUI related code flutter/shell/common/engine. The cc

- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil {
  // Launch the Dart application with the inferred run configuration.
  self.shell.GetTaskRunners().GetUITaskRunner()->PostTask(fml::MakeCopyable(
      [engine = _shell->GetEngine(),
       config = [_dartProject.get() runConfigurationForEntrypoint:entrypoint
                                                     libraryOrNil:libraryOrNil]  //] ()mutable {
        if (engine) {
          auto result = engine->Run(std::move(config));
          if (result == shell::Engine::RunStatus::Failure) {
            FML_LOG(ERROR) << "Could not launch engine with configuration."; }}})); }Copy the code

So far, the difference between IOS and Android code is the above part, after calling engine->Run function, it enters the core part of FlutteREngine, all Android and IOS running code logic is the same, please refer to the Android startup process

summary

The initialization operation of FlutterEngine on IOS feels much simpler than that on Android. The following is a summary of how FlutterEngine related logic is initialized during App startup. The above analysis and Android related parts have not been analyzed. Hell ::Shell::Create, FlutterEngine:Run, FlutterEngine core code logic, all platforms are basically consistent

1. When FlutterAppDelegateAPP is started, init is called to initialize globally related logic and lifecycle

2.FlutterViewController starts. Init starts to initialize FlutterEngine

3. Assign FlutterView to view of FlutterViewController in loadView

4. Call engine start in viewWillAppear method, load the code related to FlutterUI layer, actually find the main() related to FlutterUI layer to call you