Core principle and Simulation of small program

This article will introduce the separation architecture of the logic layer of the core view layer of the applet, and simulate this dual thread model through iOS code.

What is applet

Applet is a new mobile application format, which depends on Web technology, but also integrates native application functions.

At present, small program platforms on the market are WeChat, Alipay, Baidu, headlines, Jingdong, FanTai Some features of applets help bridge the gap between the Web and the native platform, so applets are popular with some super applications.

  • It does not need to be installed and supports hot update.
  • Multiple Web views to improve performance.
  • It provides mechanisms to access operating system functions (native interfaces) or data through native paths.
  • Its content is often more trustworthy because the application needs to be validated by the platform.
  • Applets can be distributed to multiple applets platforms (Web, native applications, and even OS). These platforms also provide an entry for applets to help users easily find the applications they need.

Core functions of applet

Separating view layer and logic layer

In applets, the view layer is usually separated from the logic layer.

  • View layer view is responsible for rendering applet pages, including Web component and native component rendering, which can be regarded as mixed rendering. For example, Web component rendering can be handled by WebView, but WebView does not support some Web component rendering, or its performance is limited; applet also depends on some native components, such as maps, videos, etc.
  • The logic layer Service is a JS logic mainly used to execute applets. It is mainly responsible for the event handling, API calling and life cycle management of applets. The extended native functions usually come from the host native application or operating system, including photographing, location, Bluetooth, network status, file processing, scanning, telephone, etc. They are called through certain APIs. When the applet calls the native API, it will pass the API call to the extended native function for further processing through JSBridge, and get the result from the extended native function through JSBridge. The Service establishes a connection for each Render and transmits the data to be rendered for further processing.
  • If the event is triggered by a component in the applet page, the page sends the event to the Service for further processing. At the same time, the page waits for the data sent by the Service to re render the applet page.
  • Render passes can be considered stateless, and all States are stored in the Service.

There are many advantages to separating view layer and logic layer:

  • Facilitate the data sharing and interaction between multiple applet pages.
  • Having the same context in the life cycle of an applet can provide a familiar coding experience for developers with a native application development background.
  • The separation and parallel implementation of Service and View can prevent JS execution from affecting or slowing down page rendering, which helps improve rendering performance.
  • Because JS executes in the Service layer, the DOM operated in JS will not affect the View layer, so applets cannot operate the DOM structure, which makes the performance of applets better than the traditional H5.

Simulation of two thread model of small program

~~Take a look at the results

Next, we will use iOS code to simulate the above two-threaded model. First of all, let's realize the data communication between the visual layer and the logical layer

As shown in the figure above, both the view layer and the logic layer implement the data receiving and sending through the JS Bridge's publish and subscribe, respectively.

Simulation Implementation

  1. The view layer calls JSBridge.publish to pass the event to the native; parameters: {eventName: '', data: {}}
//Click the button to inform JS to execute the business logic
function onTest() {
  console.log('aaa')
  FinChatJSBridge.subscribe('PAGE_EVENT', function (params) {
                            document.getElementById('testId').innerHTML = params.data.title                                })
  FinChatJSBridge.publish('PAGE_EVENT', {
    eventName: 'onTest',data: {}
  })

}
  1. The native view layer receives the page event and forwards it to the service layer for processing
if ([message.name isEqualToString:@"publishHandler"]) {
        NSString *e = message.body[@"event"];
        [self.service callSubscribeHandlerWithEvent:e param:message.body[@"paramsString"]];
    }
  1. The native service layer receives the events of the native view layer, and passes the events and parameters to the view service layer through jsbridge to execute js logic

    NSString *js = [NSString stringWithFormat:@"ServiceJSBridge.subscribeHandler('%@',%@)",eventName,jsonParam];
    [self evaluateJavaScript:js completionHandler:nil];
  2. View service. After receiving the event, execute the JS business code
var Page = {
  setData: function(data) {
    //Send update data information to native view layer
    ServiceJSBridge.publish('PAGE_EVENT', {
      eventName: 'onPageDataChange',
      data: data
    })
  },
  methods: {
    onTest: function() {
      // Execute the JS method, simulate the setData of the applet, and update the data to the view layer
      Page.setData({
        title: 'I come from JS code update'
      })
      console.log('my on Test')
    }
  }
}
var onWebviewEvent = function(fn) {
  ServiceJSBridge.subscribe('PAGE_EVENT', function(params) {
    console.log('FinChatJSBridge.subscribe')
    var data = params.data,
      eventName = params.eventName
    fn({
      data: data,
      eventName: eventName
    })
  })
}
var doWebviewEvent = function(pEvent, params) {
  // do dom ready

  if (Page.methods.hasOwnProperty(pEvent)) {
    // Receive the event of view layer and execute the method corresponding to JS
    Page.methods[pEvent].call(params)
  }
}
  1. After executing the business JS code, the data update is passed to the view layer to update the UI display data
    ServiceJSBridge.publish('PAGE_EVENT', {
    eventName: 'onPageDataChange',
    data: data
    })
  2. The native service layer receives events from the view service layer and passes them to the native view layer
    if ([message.name isEqualToString:@"publishHandler"]) {
    NSString *e = message.body[@"event"];
    [self.controller callSubscribeHandlerWithEvent:e param:message.body[@"paramsString"]];    }
  3. The native view layer passes the received events to the view view layer
    NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.subscribeHandler('%@',%@)",eventName,jsonParam];
    [self evaluateJavaScript:js completionHandler:nil];
  4. View the view layer. After receiving the event, update the interface
FinChatJSBridge.subscribe('PAGE_EVENT', function(params) {
  document.getElementById('testId').innerHTML = params.data.title
})

Subscription data callback

// Subscribe to data callback first
JSBridge.subscribe('PAGE_EVENT', function(params) {
  // ... the returned data is processed here
})
// Publish data to JS Bridge
// eventName: used to identify the event name
// Data: data passed for
JSBridge.publish('PAGE_EVENT', { eventName: 'onTest', data: {} })

WKWebView initialization,

    WKUserContentController *userContentController = [WKUserContentController new];
    NSString *souce = @"window.__fcjs_environment='miniprogram'";
    WKUserScript *script = [[WKUserScript alloc] initWithSource:souce injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:true];
    [userContentController addUserScript:script];
    [userContentController addScriptMessageHandler:self name:@"publishHandler"];

    WKWebViewConfiguration *wkWebViewConfiguration = [WKWebViewConfiguration new];
    wkWebViewConfiguration.allowsInlineMediaPlayback = YES;
    wkWebViewConfiguration.userContentController = userContentController;

    if (@available(iOS 9.0, *)) {
        [wkWebViewConfiguration.preferences setValue:@(true) forKey:@"allowFileAccessFromFileURLs"];
    }
    WKPreferences *preferences = [WKPreferences new];
    preferences.javaScriptCanOpenWindowsAutomatically = YES;
    wkWebViewConfiguration.preferences = preferences;

    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:wkWebViewConfiguration];
    self.webView.clipsToBounds = YES;
    self.webView.allowsBackForwardNavigationGestures = YES;

    [self.view addSubview:self.webView];
    NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"view.html" ofType:nil];
    NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
    [self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];

WKWebView event callback processing

// Execute view layer event callback
- (void)callSubscribeHandlerWithEvent:(NSString *)eventName param:(NSString *)jsonParam
{
    NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.subscribeHandler('%@',%@)",eventName,jsonParam];
    [self evaluateJavaScript:js completionHandler:nil];

}

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void(^)(id result,NSError *error))completionHandler
{

    [self.webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];
}

#pragma mark - WKScriptMessageHandler
// View layer JSBridge request receiving processing
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    if ([message.name isEqualToString:@"publishHandler"]) {
        NSString *e = message.body[@"event"];
        [self.service callSubscribeHandlerWithEvent:e param:message.body[@"paramsString"]];
    }
}

View layer code

function onTest() {
  console.log('aaa')
  FinChatJSBridge.subscribe('PAGE_EVENT', function(params) {
    document.getElementById('testId').innerHTML = params.data.title
  })
  FinChatJSBridge.publish('PAGE_EVENT', {
    eventName: 'onTest',
    data: {}
  })
}
<div id="testId">I come from view layer!</div>
<input type="button" value="call JS Logic layer setData" style="border-radius:15px;background:#ed0c50;border: #EDD70C;color: white;font-size: 14px; width: 80%;" onclick="onTest();" />

Logic layer code

// page image simulation
var Page = {
  setData: function(data) {
    ServiceJSBridge.publish('PAGE_EVENT', {
      eventName: 'onPageDataChange',
      data: data
    })
  },
  methods: {
    onTest: function() {
      Page.setData({
        title: 'I come from JS code update'
      })
      console.log('my on Test')
    }
  }
}
var onWebviewEvent = function(fn) {
  ServiceJSBridge.subscribe('PAGE_EVENT', function(params) {
    var data = params.data,
      eventName = params.eventName
    fn({
      data: data,
      eventName: eventName
    })
  })
}
var doWebviewEvent = function(pEvent, params) {
  // do dom ready
  if (Page.methods.hasOwnProperty(pEvent)) {
    Page.methods[pEvent].call(params)
  }
}
onWebviewEvent(function(params) {
  var eventName = params.eventName
  var data = params.data
  return doWebviewEvent(eventName, data)
})

Document center: Document

Sample code of this article: https://github.com/finogeeks/fino-applet

Contact us for more

Tags: Programming iOS Mobile hot update network

Posted on Thu, 14 May 2020 01:57:40 -0700 by Xoid