• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#define FML_USED_ON_EMBEDDER
6
7#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
8
9#include <memory>
10
11#include "flutter/fml/message_loop.h"
12#include "flutter/fml/platform/darwin/platform_version.h"
13#include "flutter/fml/trace_event.h"
14#include "flutter/shell/common/engine.h"
15#include "flutter/shell/common/platform_view.h"
16#include "flutter/shell/common/shell.h"
17#include "flutter/shell/common/switches.h"
18#include "flutter/shell/common/thread_host.h"
19#include "flutter/shell/platform/darwin/common/command_line.h"
20#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.h"
21#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
22#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h"
23#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
24#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
25#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
26#import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h"
27#import "flutter/shell/platform/darwin/ios/ios_surface.h"
28#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
29
30@interface FlutterEngine () <FlutterTextInputDelegate, FlutterBinaryMessenger>
31// Maintains a dictionary of plugin names that have registered with the engine.  Used by
32// FlutterEngineRegistrar to implement a FlutterPluginRegistrar.
33@property(nonatomic, readonly) NSMutableDictionary* pluginPublications;
34
35@property(nonatomic, readwrite, copy) NSString* isolateId;
36@end
37
38@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
39- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine;
40@end
41
42@implementation FlutterEngine {
43  fml::scoped_nsobject<FlutterDartProject> _dartProject;
44  flutter::ThreadHost _threadHost;
45  std::unique_ptr<flutter::Shell> _shell;
46  NSString* _labelPrefix;
47  std::unique_ptr<fml::WeakPtrFactory<FlutterEngine>> _weakFactory;
48
49  fml::WeakPtr<FlutterViewController> _viewController;
50  fml::scoped_nsobject<FlutterObservatoryPublisher> _publisher;
51
52  std::unique_ptr<flutter::FlutterPlatformViewsController> _platformViewsController;
53
54  // Channels
55  fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin;
56  fml::scoped_nsobject<FlutterTextInputPlugin> _textInputPlugin;
57  fml::scoped_nsobject<FlutterMethodChannel> _localizationChannel;
58  fml::scoped_nsobject<FlutterMethodChannel> _navigationChannel;
59  fml::scoped_nsobject<FlutterMethodChannel> _platformChannel;
60  fml::scoped_nsobject<FlutterMethodChannel> _platformViewsChannel;
61  fml::scoped_nsobject<FlutterMethodChannel> _textInputChannel;
62  fml::scoped_nsobject<FlutterBasicMessageChannel> _lifecycleChannel;
63  fml::scoped_nsobject<FlutterBasicMessageChannel> _systemChannel;
64  fml::scoped_nsobject<FlutterBasicMessageChannel> _settingsChannel;
65
66  int64_t _nextTextureId;
67
68  BOOL _allowHeadlessExecution;
69  FlutterBinaryMessengerRelay* _binaryMessenger;
70}
71
72- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)projectOrNil {
73  return [self initWithName:labelPrefix project:projectOrNil allowHeadlessExecution:YES];
74}
75
76- (instancetype)initWithName:(NSString*)labelPrefix
77                     project:(FlutterDartProject*)projectOrNil
78      allowHeadlessExecution:(BOOL)allowHeadlessExecution {
79  self = [super init];
80  NSAssert(self, @"Super init cannot be nil");
81  NSAssert(labelPrefix, @"labelPrefix is required");
82
83  _allowHeadlessExecution = allowHeadlessExecution;
84  _labelPrefix = [labelPrefix copy];
85
86  _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(self);
87
88  if (projectOrNil == nil)
89    _dartProject.reset([[FlutterDartProject alloc] init]);
90  else
91    _dartProject.reset([projectOrNil retain]);
92
93  _pluginPublications = [NSMutableDictionary new];
94  _platformViewsController.reset(new flutter::FlutterPlatformViewsController());
95
96  [self setupChannels];
97  _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
98
99  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
100  [center addObserver:self
101             selector:@selector(onMemoryWarning:)
102                 name:UIApplicationDidReceiveMemoryWarningNotification
103               object:nil];
104
105  return self;
106}
107
108- (void)dealloc {
109  [_pluginPublications release];
110  _binaryMessenger.parent = nil;
111  [_binaryMessenger release];
112
113  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
114  [center removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
115
116  [super dealloc];
117}
118
119- (flutter::Shell&)shell {
120  FML_DCHECK(_shell);
121  return *_shell;
122}
123
124- (fml::WeakPtr<FlutterEngine>)getWeakPtr {
125  return _weakFactory->GetWeakPtr();
126}
127
128- (void)updateViewportMetrics:(flutter::ViewportMetrics)viewportMetrics {
129  if (!self.platformView) {
130    return;
131  }
132  self.platformView->SetViewportMetrics(std::move(viewportMetrics));
133}
134
135- (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet {
136  if (!self.platformView) {
137    return;
138  }
139  self.platformView->DispatchPointerDataPacket(std::move(packet));
140}
141
142- (fml::WeakPtr<flutter::PlatformView>)platformView {
143  FML_DCHECK(_shell);
144  return _shell->GetPlatformView();
145}
146
147- (flutter::PlatformViewIOS*)iosPlatformView {
148  FML_DCHECK(_shell);
149  return static_cast<flutter::PlatformViewIOS*>(_shell->GetPlatformView().get());
150}
151
152- (fml::RefPtr<fml::TaskRunner>)platformTaskRunner {
153  FML_DCHECK(_shell);
154  return _shell->GetTaskRunners().GetPlatformTaskRunner();
155}
156
157- (fml::RefPtr<fml::TaskRunner>)GPUTaskRunner {
158  FML_DCHECK(_shell);
159  return _shell->GetTaskRunners().GetGPUTaskRunner();
160}
161
162- (void)ensureSemanticsEnabled {
163  self.iosPlatformView->SetSemanticsEnabled(true);
164}
165
166- (void)setViewController:(FlutterViewController*)viewController {
167  FML_DCHECK(self.iosPlatformView);
168  _viewController = [viewController getWeakPtr];
169  self.iosPlatformView->SetOwnerViewController(_viewController);
170  [self maybeSetupPlatformViewChannels];
171}
172
173- (void)notifyViewControllerDeallocated {
174  if (!_allowHeadlessExecution) {
175    [self destroyContext];
176  }
177}
178
179- (void)destroyContext {
180  [self resetChannels];
181  self.isolateId = nil;
182  _shell.reset();
183  _threadHost.Reset();
184  _platformViewsController.reset();
185}
186
187- (FlutterViewController*)viewController {
188  if (!_viewController) {
189    return nil;
190  }
191  return _viewController.get();
192}
193
194- (FlutterPlatformPlugin*)platformPlugin {
195  return _platformPlugin.get();
196}
197- (flutter::FlutterPlatformViewsController*)platformViewsController {
198  return _platformViewsController.get();
199}
200- (FlutterTextInputPlugin*)textInputPlugin {
201  return _textInputPlugin.get();
202}
203- (FlutterMethodChannel*)localizationChannel {
204  return _localizationChannel.get();
205}
206- (FlutterMethodChannel*)navigationChannel {
207  return _navigationChannel.get();
208}
209- (FlutterMethodChannel*)platformChannel {
210  return _platformChannel.get();
211}
212- (FlutterMethodChannel*)textInputChannel {
213  return _textInputChannel.get();
214}
215- (FlutterBasicMessageChannel*)lifecycleChannel {
216  return _lifecycleChannel.get();
217}
218- (FlutterBasicMessageChannel*)systemChannel {
219  return _systemChannel.get();
220}
221- (FlutterBasicMessageChannel*)settingsChannel {
222  return _settingsChannel.get();
223}
224
225- (NSURL*)observatoryUrl {
226  return [_publisher.get() url];
227}
228
229- (void)resetChannels {
230  _localizationChannel.reset();
231  _navigationChannel.reset();
232  _platformChannel.reset();
233  _platformViewsChannel.reset();
234  _textInputChannel.reset();
235  _lifecycleChannel.reset();
236  _systemChannel.reset();
237  _settingsChannel.reset();
238}
239
240// If you add a channel, be sure to also update `resetChannels`.
241// Channels get a reference to the engine, and therefore need manual
242// cleanup for proper collection.
243- (void)setupChannels {
244  // This will be invoked once the shell is done setting up and the isolate ID
245  // for the UI isolate is available.
246  [_binaryMessenger setMessageHandlerOnChannel:@"flutter/isolate"
247                          binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
248                            self.isolateId = [[FlutterStringCodec sharedInstance] decode:message];
249                          }];
250
251  _localizationChannel.reset([[FlutterMethodChannel alloc]
252         initWithName:@"flutter/localization"
253      binaryMessenger:self.binaryMessenger
254                codec:[FlutterJSONMethodCodec sharedInstance]]);
255
256  _navigationChannel.reset([[FlutterMethodChannel alloc]
257         initWithName:@"flutter/navigation"
258      binaryMessenger:self.binaryMessenger
259                codec:[FlutterJSONMethodCodec sharedInstance]]);
260
261  _platformChannel.reset([[FlutterMethodChannel alloc]
262         initWithName:@"flutter/platform"
263      binaryMessenger:self.binaryMessenger
264                codec:[FlutterJSONMethodCodec sharedInstance]]);
265
266  _platformViewsChannel.reset([[FlutterMethodChannel alloc]
267         initWithName:@"flutter/platform_views"
268      binaryMessenger:self.binaryMessenger
269                codec:[FlutterStandardMethodCodec sharedInstance]]);
270
271  _textInputChannel.reset([[FlutterMethodChannel alloc]
272         initWithName:@"flutter/textinput"
273      binaryMessenger:self.binaryMessenger
274                codec:[FlutterJSONMethodCodec sharedInstance]]);
275
276  _lifecycleChannel.reset([[FlutterBasicMessageChannel alloc]
277         initWithName:@"flutter/lifecycle"
278      binaryMessenger:self.binaryMessenger
279                codec:[FlutterStringCodec sharedInstance]]);
280
281  _systemChannel.reset([[FlutterBasicMessageChannel alloc]
282         initWithName:@"flutter/system"
283      binaryMessenger:self.binaryMessenger
284                codec:[FlutterJSONMessageCodec sharedInstance]]);
285
286  _settingsChannel.reset([[FlutterBasicMessageChannel alloc]
287         initWithName:@"flutter/settings"
288      binaryMessenger:self.binaryMessenger
289                codec:[FlutterJSONMessageCodec sharedInstance]]);
290
291  _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
292  _textInputPlugin.get().textInputDelegate = self;
293
294  _platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]);
295}
296
297- (void)maybeSetupPlatformViewChannels {
298  if (_shell && self.shell.IsSetup()) {
299    [_platformChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
300      [_platformPlugin.get() handleMethodCall:call result:result];
301    }];
302
303    [_platformViewsChannel.get()
304        setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
305          _platformViewsController->OnMethodCall(call, result);
306        }];
307
308    [_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
309      [_textInputPlugin.get() handleMethodCall:call result:result];
310    }];
311    self.iosPlatformView->SetTextInputPlugin(_textInputPlugin);
312  }
313}
314
315- (flutter::Rasterizer::Screenshot)screenshot:(flutter::Rasterizer::ScreenshotType)type
316                                 base64Encode:(bool)base64Encode {
317  return self.shell.Screenshot(type, base64Encode);
318}
319
320- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil {
321  // Launch the Dart application with the inferred run configuration.
322  self.shell.RunEngine([_dartProject.get() runConfigurationForEntrypoint:entrypoint
323                                                            libraryOrNil:libraryOrNil]);
324}
325
326- (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
327  if (_shell != nullptr) {
328    FML_LOG(WARNING) << "This FlutterEngine was already invoked.";
329    return NO;
330  }
331
332  static size_t shellCount = 1;
333
334  auto settings = [_dartProject.get() settings];
335
336  if (libraryURI) {
337    FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library";
338    settings.advisory_script_entrypoint = entrypoint.UTF8String;
339    settings.advisory_script_uri = libraryURI.UTF8String;
340  } else if (entrypoint) {
341    settings.advisory_script_entrypoint = entrypoint.UTF8String;
342    settings.advisory_script_uri = std::string("main.dart");
343  } else {
344    settings.advisory_script_entrypoint = std::string("main");
345    settings.advisory_script_uri = std::string("main.dart");
346  }
347
348  const auto threadLabel = [NSString stringWithFormat:@"%@.%zu", _labelPrefix, shellCount++];
349  FML_DLOG(INFO) << "Creating threadHost for " << threadLabel.UTF8String;
350  // The current thread will be used as the platform thread. Ensure that the message loop is
351  // initialized.
352  fml::MessageLoop::EnsureInitializedForCurrentThread();
353
354  _threadHost = {threadLabel.UTF8String,  // label
355                 flutter::ThreadHost::Type::UI | flutter::ThreadHost::Type::GPU |
356                     flutter::ThreadHost::Type::IO};
357
358  // Lambda captures by pointers to ObjC objects are fine here because the
359  // create call is
360  // synchronous.
361  flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
362      [](flutter::Shell& shell) {
363        return std::make_unique<flutter::PlatformViewIOS>(shell, shell.GetTaskRunners());
364      };
365
366  flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
367      [](flutter::Shell& shell) {
368        return std::make_unique<flutter::Rasterizer>(shell, shell.GetTaskRunners());
369      };
370
371  if (flutter::IsIosEmbeddedViewsPreviewEnabled()) {
372    // Embedded views requires the gpu and the platform views to be the same.
373    // The plan is to eventually dynamically merge the threads when there's a
374    // platform view in the layer tree.
375    // For now we use a fixed thread configuration with the same thread used as the
376    // gpu and platform task runner.
377    // TODO(amirh/chinmaygarde): remove this, and dynamically change the thread configuration.
378    // https://github.com/flutter/flutter/issues/23975
379
380    flutter::TaskRunners task_runners(threadLabel.UTF8String,                          // label
381                                      fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
382                                      fml::MessageLoop::GetCurrent().GetTaskRunner(),  // gpu
383                                      _threadHost.ui_thread->GetTaskRunner(),          // ui
384                                      _threadHost.io_thread->GetTaskRunner()           // io
385    );
386    // Create the shell. This is a blocking operation.
387    _shell = flutter::Shell::Create(std::move(task_runners),  // task runners
388                                    std::move(settings),      // settings
389                                    on_create_platform_view,  // platform view creation
390                                    on_create_rasterizer      // rasterzier creation
391    );
392  } else {
393    flutter::TaskRunners task_runners(threadLabel.UTF8String,                          // label
394                                      fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
395                                      _threadHost.gpu_thread->GetTaskRunner(),         // gpu
396                                      _threadHost.ui_thread->GetTaskRunner(),          // ui
397                                      _threadHost.io_thread->GetTaskRunner()           // io
398    );
399    // Create the shell. This is a blocking operation.
400    _shell = flutter::Shell::Create(std::move(task_runners),  // task runners
401                                    std::move(settings),      // settings
402                                    on_create_platform_view,  // platform view creation
403                                    on_create_rasterizer      // rasterzier creation
404    );
405  }
406
407  if (_shell == nullptr) {
408    FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: "
409                   << entrypoint.UTF8String;
410  } else {
411    [self setupChannels];
412    if (!_platformViewsController) {
413      _platformViewsController.reset(new flutter::FlutterPlatformViewsController());
414    }
415    _publisher.reset([[FlutterObservatoryPublisher alloc] init]);
416    [self maybeSetupPlatformViewChannels];
417  }
418
419  return _shell != nullptr;
420}
421
422- (BOOL)runWithEntrypoint:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
423  if ([self createShell:entrypoint libraryURI:libraryURI]) {
424    [self launchEngine:entrypoint libraryURI:libraryURI];
425  }
426
427  return _shell != nullptr;
428}
429
430- (BOOL)runWithEntrypoint:(NSString*)entrypoint {
431  return [self runWithEntrypoint:entrypoint libraryURI:nil];
432}
433
434#pragma mark - Text input delegate
435
436- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
437  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
438                              arguments:@[ @(client), state ]];
439}
440
441- (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state
442                  withClient:(int)client
443                withPosition:(NSDictionary*)position {
444  NSString* stateString;
445  switch (state) {
446    case FlutterFloatingCursorDragStateStart:
447      stateString = @"FloatingCursorDragState.start";
448      break;
449    case FlutterFloatingCursorDragStateUpdate:
450      stateString = @"FloatingCursorDragState.update";
451      break;
452    case FlutterFloatingCursorDragStateEnd:
453      stateString = @"FloatingCursorDragState.end";
454      break;
455  }
456  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateFloatingCursor"
457                              arguments:@[ @(client), stateString, position ]];
458}
459
460- (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
461  NSString* actionString;
462  switch (action) {
463    case FlutterTextInputActionUnspecified:
464      // Where did the term "unspecified" come from? iOS has a "default" and Android
465      // has "unspecified." These 2 terms seem to mean the same thing but we need
466      // to pick just one. "unspecified" was chosen because "default" is often a
467      // reserved word in languages with switch statements (dart, java, etc).
468      actionString = @"TextInputAction.unspecified";
469      break;
470    case FlutterTextInputActionDone:
471      actionString = @"TextInputAction.done";
472      break;
473    case FlutterTextInputActionGo:
474      actionString = @"TextInputAction.go";
475      break;
476    case FlutterTextInputActionSend:
477      actionString = @"TextInputAction.send";
478      break;
479    case FlutterTextInputActionSearch:
480      actionString = @"TextInputAction.search";
481      break;
482    case FlutterTextInputActionNext:
483      actionString = @"TextInputAction.next";
484      break;
485    case FlutterTextInputActionContinue:
486      actionString = @"TextInputAction.continue";
487      break;
488    case FlutterTextInputActionJoin:
489      actionString = @"TextInputAction.join";
490      break;
491    case FlutterTextInputActionRoute:
492      actionString = @"TextInputAction.route";
493      break;
494    case FlutterTextInputActionEmergencyCall:
495      actionString = @"TextInputAction.emergencyCall";
496      break;
497    case FlutterTextInputActionNewline:
498      actionString = @"TextInputAction.newline";
499      break;
500  }
501  [_textInputChannel.get() invokeMethod:@"TextInputClient.performAction"
502                              arguments:@[ @(client), actionString ]];
503}
504
505#pragma mark - Screenshot Delegate
506
507- (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
508                                  asBase64Encoded:(BOOL)base64Encode {
509  FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell";
510  return _shell->Screenshot(type, base64Encode);
511}
512
513- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
514  return _binaryMessenger;
515}
516
517#pragma mark - FlutterBinaryMessenger
518
519- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
520  [self sendOnChannel:channel message:message binaryReply:nil];
521}
522
523- (void)sendOnChannel:(NSString*)channel
524              message:(NSData*)message
525          binaryReply:(FlutterBinaryReply)callback {
526  NSAssert(channel, @"The channel must not be null");
527  fml::RefPtr<flutter::PlatformMessageResponseDarwin> response =
528      (callback == nil) ? nullptr
529                        : fml::MakeRefCounted<flutter::PlatformMessageResponseDarwin>(
530                              ^(NSData* reply) {
531                                callback(reply);
532                              },
533                              _shell->GetTaskRunners().GetPlatformTaskRunner());
534  fml::RefPtr<flutter::PlatformMessage> platformMessage =
535      (message == nil) ? fml::MakeRefCounted<flutter::PlatformMessage>(channel.UTF8String, response)
536                       : fml::MakeRefCounted<flutter::PlatformMessage>(
537                             channel.UTF8String, flutter::GetVectorFromNSData(message), response);
538
539  _shell->GetPlatformView()->DispatchPlatformMessage(platformMessage);
540}
541
542- (void)setMessageHandlerOnChannel:(NSString*)channel
543              binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
544  NSAssert(channel, @"The channel must not be null");
545  FML_DCHECK(_shell && _shell->IsSetup());
546  self.iosPlatformView->GetPlatformMessageRouter().SetMessageHandler(channel.UTF8String, handler);
547}
548
549#pragma mark - FlutterTextureRegistry
550
551- (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture {
552  int64_t textureId = _nextTextureId++;
553  self.iosPlatformView->RegisterExternalTexture(textureId, texture);
554  return textureId;
555}
556
557- (void)unregisterTexture:(int64_t)textureId {
558  _shell->GetPlatformView()->UnregisterTexture(textureId);
559}
560
561- (void)textureFrameAvailable:(int64_t)textureId {
562  _shell->GetPlatformView()->MarkTextureFrameAvailable(textureId);
563}
564
565- (NSString*)lookupKeyForAsset:(NSString*)asset {
566  return [FlutterDartProject lookupKeyForAsset:asset];
567}
568
569- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
570  return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
571}
572
573- (id<FlutterPluginRegistry>)pluginRegistry {
574  return self;
575}
576
577#pragma mark - FlutterPluginRegistry
578
579- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
580  NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey);
581  self.pluginPublications[pluginKey] = [NSNull null];
582  return [[[FlutterEngineRegistrar alloc] initWithPlugin:pluginKey flutterEngine:self] autorelease];
583}
584
585- (BOOL)hasPlugin:(NSString*)pluginKey {
586  return _pluginPublications[pluginKey] != nil;
587}
588
589- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
590  return _pluginPublications[pluginKey];
591}
592
593#pragma mark - Memory Notifications
594
595- (void)onMemoryWarning:(NSNotification*)notification {
596  if (_shell) {
597    _shell->NotifyLowMemoryWarning();
598  }
599  [_systemChannel sendMessage:@{@"type" : @"memoryPressure"}];
600}
601
602@end
603
604@implementation FlutterEngineRegistrar {
605  NSString* _pluginKey;
606  FlutterEngine* _flutterEngine;
607}
608
609- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine {
610  self = [super init];
611  NSAssert(self, @"Super init cannot be nil");
612  _pluginKey = [pluginKey retain];
613  _flutterEngine = [flutterEngine retain];
614  return self;
615}
616
617- (void)dealloc {
618  [_pluginKey release];
619  [_flutterEngine release];
620  [super dealloc];
621}
622
623- (NSObject<FlutterBinaryMessenger>*)messenger {
624  return _flutterEngine.binaryMessenger;
625}
626
627- (NSObject<FlutterTextureRegistry>*)textures {
628  return _flutterEngine;
629}
630
631- (void)publish:(NSObject*)value {
632  _flutterEngine.pluginPublications[_pluginKey] = value;
633}
634
635- (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
636                      channel:(FlutterMethodChannel*)channel {
637  [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
638    [delegate handleMethodCall:call result:result];
639  }];
640}
641
642- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
643  id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
644  if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
645    id<FlutterAppLifeCycleProvider> lifeCycleProvider =
646        (id<FlutterAppLifeCycleProvider>)appDelegate;
647    [lifeCycleProvider addApplicationLifeCycleDelegate:delegate];
648  }
649}
650
651- (NSString*)lookupKeyForAsset:(NSString*)asset {
652  return [_flutterEngine lookupKeyForAsset:asset];
653}
654
655- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
656  return [_flutterEngine lookupKeyForAsset:asset fromPackage:package];
657}
658
659- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
660                     withId:(NSString*)factoryId {
661  [_flutterEngine platformViewsController] -> RegisterViewFactory(factory, factoryId);
662}
663
664@end
665