Writing a Hybrid Framework that is easy to maintain, convenient to use and reliable in performance (I) — Idea Construction

“Write a Hybrid Framework that is easy to maintain, convenient to use and reliable in performance (II) — Plug-in”

“Write a Hybrid Framework that is easy to maintain, convenient to use and reliable in performance (III) — Configuration Plug-in”

“Write a Hybrid Framework that is easy to maintain, convenient to use and reliable in performance (IV) — Framework Construction”

preface

After the previous, I repeatedly reasoning, my next article what to write, so idea, I should be how to carry out, how do I design framework in code level, how to use as convenient as possible, so well, I deeply felt, the previous I dug a hole for yourself, recent thoughts has been on Cordova framework of design patterns, Tried to jump up out of the truth is hard, I really hard to come up with a better than it pluggable part design, so the plugin this piece, I still continue Cordova, cut off part of work in less than at ordinary times, it is more convenient to tend to the direction of the design, so, today I wrote a short demo, js and native side communication has been basically achieved, Next, I will write the second part of Hybrid framework based on demo. First, I will talk about the plug-in part of native end.

To the chase

In terms of frame usage and JS-Bridge, I will continue the two blog posts mentioned above, that is, webView is not provided in the framework, and webView is implemented by users themselves. I don’t care about all webView, I am only responsible for providing Hybrid capability for your webView. The second point is the communication based on WKWebView addScriptMessageHandler method, abandon UIWebView URL interception method.

When WKWebView is initialized, there is a parameter called Configuration, It’s a parameter of type WKWebViewConfiguration, and WKWebViewConfiguration has a property called userContentController, which in turn is a parameter of type WKUserContentController. WKUserContentController object has a method – addScriptMessageHandler: name:, the first parameter is the userContentController proxy objects, The second argument corresponds to the postMessage object on the JS side. Since set up a proxy object, of course, to realize its proxy approach, namely WKScriptMessageHandler protocols provide userContentController: didReceiveScriptMessage: function, with them, we can communicate, WKWebView communication is that simple.

Let’s take a look at how my Hybrid framework works, based on the ideas mentioned in the previous article. Let’s look directly at the code:

#import "ViewController.h"
#import <WebKit/WebKit.h>
#import "SHRMWebViewEngine.h"@interface ViewController ()<WKNavigationDelegate> @property (strong, nonatomic) WKWebView *webView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; WKPreferences *preferences = [WKPreferences new]; preferences.javaScriptCanOpenWindowsAutomatically = YES; Preferences. MinimumFontSize = 40.0; configuration.preferences = preferences; self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration]; /***/ SHRMWebViewEngine *jsBridge = [[SHRMWebViewEngine alloc] init]; jsBridge.delegate = self; [jsBridgebindBridgeWithWebView:self.webView];
    /***/
    
    NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
    NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
    if(@ the available (iOS 9.0. *)) {[self. WebView loadFileURL: fileURL allowingReadAccessToURL: fileURL]; }else {
        // Fallback on earlier versions
    }
    [self.view addSubview:self.webView];
}


@end
Copy the code

SHRMWebViewEngine *jsBridge = [[SHRMWebViewEngine alloc] init]; jsBridge.delegate = self; [jsBridge bindBridgeWithWebView:self.webView]; In fact, these three lines of code make your webView have the ability to Hybird, is not very convenient to use, in fact, there is nothing to say, this idea is also before the big guy mentioned, I just do a summary of it. So let’s go through the code and see what I did in there.

#import "SHRMWebViewEngine.h"
#import "SHRMWebViewDelegate.h"
#import "SHRMWebViewHandleFactory.h"

@interface SHRMWebViewEngine ()
@property (nonatomic, strong) SHRMWebViewDelegate *webViewDelegate;
@property (nonatomic, strong) SHRMWebViewHandleFactory *webViewhandleFactory;
@end

@implementation SHRMWebViewEngine

- (instancetype)init {
    if (self = [super init]) {
        _webViewhandleFactory = [[SHRMWebViewHandleFactory alloc] initWithWebViewEngine:self];
        _webViewDelegate = [[SHRMWebViewDelegate alloc] initWithWebViewEngine:self];
    }
    return self;
}

- (void)bindBridgeWithWebView:(WKWebView *)webView {
    self.webView = webView;
    if(! [_delegate conformsToProtocol:@protocol(WKUIDelegate)]) { self.webView.UIDelegate = _webViewDelegate; }if(! [_delegate conformsToProtocol:@protocol(WKNavigationDelegate)]) { self.webView.navigationDelegate = _webViewDelegate; } webView.configuration.userContentController = [[WKUserContentController alloc] init]; [webView.configuration.userContentController addScriptMessageHandler:self name:@"SHRMWKJSBridge"];
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if([message.body isKindOfClass:[NSArray class]]) { [_webViewhandleFactory handleMsgCommand:message.body]; }}#pragma mark - SHRMWebViewProtocol

- (void)sendPluginResult:(NSString *)result callbackId:(NSString*)callbackId {
    NSString *jsStr = [NSString stringWithFormat:@"fetchComplete('(%@)','%@')",callbackId,result];
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];
}

- (void)runInBackground:(void (^)(void))block {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
}

#pragma mark - dealloc

- (void)dealloc {
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"SHRMWKJSBridge"];
}

@end
Copy the code

1. SHRMWebViewHandleFactory object, is responsible for executing native server-side plug-in call process, internal will make the factory in the future.

2.SHRMWebViewDelegate object, responsible for WKWebView proxy implementation, MY idea is that if the developer does not have their own implementation of WKWebView proxy, then I default to the framework provided by the proxy method, including the progress bar.

3. BindBridgeWithWebView: The function does the binding of webView and proxy, mainly to get the webView that wants to acquire Hybrid capability. So we get this webView, and we can do the next communication. SHRMWKJSBridge is a custom JS side postMessage object (which matches the above mentioned object), and is a unified entry point to jsBridge, which will be discussed later in order to make the comparison clearer.

4. UserContentController: didReceiveScriptMessage: get js passed parameters, how parameters are passed a glued to the js code later.

5.SHRMWebViewProtocol: it provides two interfaces, one is native callback JS interface, the other is to open up the sub-thread to execute time-consuming operation interface. Through code can see native js use callback is evaluateJavaScript: completionHandler: function, it is provided after WKWebView, natural and asynchronous execution, There is no need for the developer to manually handle the asynchronous problem of js callback to the native side like UIWebView.

So the SHRMWebViewEngine core class really only does a few things so far, and of course this is just a demo I wrote today, and I’m going to optimize the code and tune the communication, so let’s go step by step.

@protocol SHRMWebViewProtocol <NSObject>

/**
 native call back js

 @param result simulate data
 @param callbackId callbackId
 */
- (void)sendPluginResult:(NSString *)result callbackId:(NSString*)callbackId;

/**
background

 @param block long running
 */
- (void)runInBackground:(void (^)(void))block;
@end
Copy the code

This is where the interface definition of Cordova is just seen. Today, I have been thinking that all the custom plugins of Cordova need to inherit from the CDVPlugin base class. Its callback interface implementation class CDVPlugin is provided in the base class, so it can use the CDVPlugin base class to carry out the callback of native js. I made a simple change to this area, removing the CDVPlugin base class, because our plugin doesn’t need to inherit from any other classes, just from NSObject. Again, look at the code:

@class SHRMMsgCommand;
@interface SHRMFetchPlugin : NSObject
- (void)nativeFentch:(SHRMMsgCommand *)command;
@end
@implementation SHRMFetchPlugin
- (void)nativeFentch:(SHRMMsgCommand *)command {
    NSString *method = [command argumentAtIndex:0];
    NSString *url = [command argumentAtIndex:1];
    NSString *param = [command argumentAtIndex:2];
    NSLog(@"(% @) : % @, % @, % @",command.callbackId, method, url, param);
    [command.delegate sendPluginResult:@"fetch success" callbackId:command.callbackId];
}
@end

Copy the code

Inevitably, I still need the SHRMMsgCommand object, otherwise the plug-in won’t be able to communicate with the framework. SHRMMsgCommand does not say what it does. It is actually a parameter acceptor passed by JS and stores parameter information for use by plug-ins. Delegate is actually an object of type id

delegate, because I’m currently calling the callback interface into the SHRMWebViewEngine, which is actually the delegate. The main purpose of this is to understand the coupling so that SHRMMsgCommand does not have to reference the SHRMWebViewEngine header at all. This is the plug-in provided by the native end of network request simulation. What is a plug-in, as the name implies, is not coupled to the framework. In fact, there is no need to introduce SHRMFetchPlugin in the framework.

Here we have basically realized the simple plug-in in the native end, and the communication between JS and Native based on WKWebView has also been realized. So I’m calling it directly on the JS side

window.webkit.messageHandlers.SHRMWKJSBridge.postMessage(['13383445'.'SHRMFetchPlugin'.'nativeFentch'['post'.'https:www.baidu.com'.'user']]);
Copy the code

SHRMWKJSBridge is the object I defined above to send a postMessage. Of course, native js callback requires js to provide a global function for native to call. I’ve defined a fetchComplete function in the demo:

function fetchComplete(id,result) {
                asyncAlert(result);
                document.getElementById("returnValue").value = (id) + result;
            }
            
            function asyncAlert(content) {
                setTimeout(function(){ alert(content); }, 1); }Copy the code

Here’s the code for the js callback above:

- (void)sendPluginResult:(NSString *)result callbackId:(NSString*)callbackId {
    NSString *jsStr = [NSString stringWithFormat:@"fetchComplete('(%@)','%@')",callbackId,result];
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];
}
Copy the code

The be clear at a glance, so that the whole process of call ended, window, of course. Its. MessageHandlers. SHRMWKJSBridge. PostMessage and fetchComplete later we will encapsulate them separately, For front-end developers, there will only be a unified call and callback interface, more on that later. If it is just for the simple implementation of functions, in fact, it can be done here. But after all, we are building a framework, so it is far from enough. I will stop here first for two articles (PS: mainly for the demo, in addition, I have to go to bed early).

conclusion

To summarize, this paper has simply implemented the plug-in of native terminal, based on the construction of WKWebView communication. The framework does not provide the three functions of webView realization, which is also consistent with our expectation in the last article, but far from enough. So we will continue in the third chapter. So the next chapter will focus on the configuration of native side plug-in and THE encapsulation of JS side interface (this is really difficult for me this did not start the front end).