繼上一篇以後,我反覆思來想去,我下一篇該怎麼寫,那麼想法有了,我應該怎麼去落實,框架在代碼層面我要怎麼設計,怎麼樣才能使用起來儘量的方便,那麼好吧,我深深的以爲,上一篇我給本身挖了個大坑,最近的思想一直依託於Cordova框架的設計模式,說實話想跳出來很難,我真的很難很難想出一個比它插件化部分更好設計,因此插件化這一塊,我依舊延續Cordova思想,精簡掉平時工做用不到的部分,偏向於更方便的方向設計,因此,今天我寫了個簡短的demo,基本實現了js和native端的通信,下面依託demo來寫個人Hybrid框架第二篇,先聊聊native端插件化部分。html
在框架使用層面和js-bridge方面,依舊延續上篇提到的兩篇博文,也就是框架內不提供webView,webView由使用者本身實現,webView的一切我不關心,我只負責給你的webView提供Hybrid能力。第二點是通訊上基於WKWebView的addScriptMessageHandler
方式,棄用UIWebView的URL攔截方式。前端
仍是簡單說一下WKWebView的addScriptMessageHandler
的通訊問題,只是簡單介紹下就不詳細講解它的通訊過程了,WKWebView初始化時,有一個參數叫configuratio
n,它是WKWebViewConfiguration
類型的參數,而WKWebViewConfiguration
有一個屬性叫userContentController
,它又是WKUserContentController
類型的參數。WKUserContentController
對象有一個方法-addScriptMessageHandler:name:
,第一個參數是userContentController
的代理對象,第二個參數對應着js端postMessage
的對象(本篇看完你就明白了沒啥說的)。既然設置了代理對象,固然還要實現它的代理方法,也就是WKScriptMessageHandler
協議提供的userContentController:didReceiveScriptMessage:
函數,有了他倆,咱們就能夠進行通訊了,WKWebView通訊就是這麼簡單。java
先看一下個人Hybrid框架是怎麼使用的,固然思想仍是基於上篇提到的大佬,直接看代碼吧:web
#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;
[jsBridge bindBridgeWithWebView:self.webView];
/***/
NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
if (@available(iOS 9.0, *)) {
[self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];
} else {
// Fallback on earlier versions
}
[self.view addSubview:self.webView];
}
@end
複製代碼
這其實應該是框架使用者要編寫的代碼,看上去很常規,中間多了三行代碼,SHRMWebViewEngine *jsBridge = [[SHRMWebViewEngine alloc] init];jsBridge.delegate = self;[jsBridge bindBridgeWithWebView:self.webView];
,實際上這三行代碼就讓你的webView具備了Hybird的能力了,用起來是否是很方便,其實這沒啥說的,這個思想也是以前大佬提過的了,我就當是作了個總結吧。那再經過代碼看一下我在裏面都作了什麼吧。設計模式
#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
複製代碼
1.
SHRMWebViewHandleFactory
對象,負責執行native端插件的調用過程,內部未來會作成工廠。bash
2.
SHRMWebViewDelegate
對象,負責WKWebView的代理實現,個人思路是若是開發者沒有本身實現WKWebView的代理,那麼默認我會走框架內提供的代理方法,包括進度條等。網絡
3.
bindBridgeWithWebView:
函數作了webView的綁定和代理的綁定,主要仍是爲了拿到想要得到Hybrid能力的webView。拿到這個webView,咱們就能夠作接下來的通訊了。SHRMWKJSBridge
爲自定義的js端postMessage的對象(和上面提到的匹配上了),是jsBridge的統一入口,這個SHRMWKJSBridge
一會下面還會說到,再對比下就更清楚了。架構
4.
userContentController:didReceiveScriptMessage:
拿到js傳遞過來的參數,參數怎麼傳遞過來的一會會粘js端的代碼。框架
5.
SHRMWebViewProtocol:
提供了兩個接口,一個是native回調js接口,一個是開闢子線程執行耗時操做接口。經過代碼能夠看到native回調js使用的是evaluateJavaScript:completionHandler:
函數,這是WKWebView以後提供的,自然異步執行,不須要像UIWebView那樣要開發者手動處理js端回調native端的異步問題。異步
那麼實際上SHRMWebViewEngine
核心類目前只作了這幾件事情,固然這只是我今天寫的demo,後續會對代碼進行優化和對通訊調優,咱們先一步一步來。
@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
複製代碼
這個就是咱們剛纔看到的接口定義的地方,今天一直在想,Cordova在插件處理的時候,咱們自定義插件都是須要繼承自CDVPlugin
基類的(沒用過不要緊,就理解爲一個提供了js回調native接口的基類就好了),它的回調接口實現類CDVPlugin基類裏面有提供,因此它能夠經過CDVPlugin基類來進行native端對js的回調。關於這一塊我作了簡單改造,移除了CDVPlugin基類,由於咱們的插件徹底能夠不用繼承任何其餘的類,只須要繼承自NSObject就好,仍是看代碼:
@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
複製代碼
不可避免,我仍是須要SHRMMsgCommand
這個對象,否則插件沒法和框架構成聯繫。SHRMMsgCommand
上面沒有說幹嗎的,它實際上只是js傳遞過來的參數接受者,存儲着參數信息供插件使用。command.delegate
實際是id <SHRMWebViewProtocol> delegate
類型的對象,由於我目前是把回調接口是如今了SHRMWebViewEngine
裏面,實際上這個delegate就是它。這麼作的目的主要是爲了解耦合,這樣SHRMMsgCommand
徹底沒必要引用SHRMWebViewEngine
的頭文件了。這個就是模擬網絡請求native端提供的插件,什麼是插件,顧名思義,就是不跟框架產生耦合,實際上框架內是不須要引入SHRMFetchPlugin的,若是說哪天這個插件不用了,直接把這個類刪了就能夠了。
到這裏咱們在native端的簡單插件化基本實現了,js與native間基於WKWebView的通訊也實現了。這樣我在js端直接調用
window.webkit.messageHandlers.SHRMWKJSBridge.postMessage(['13383445','SHRMFetchPlugin','nativeFentch',['post','https:www.baidu.com','user']]);
複製代碼
就能夠實現js call native了,SHRMWKJSBridge
(這個到這裏就很明白了吧)就是我上面定義的發送postMessage
的對象,固然native回調js是須要js提供一個全局函數供給native來調用的,我在demo裏面定義了一個fetchComplete
函數:
function fetchComplete(id,result) {
asyncAlert(result);
document.getElementById("returnValue").value = (id) + result;
}
function asyncAlert(content) {
setTimeout(function(){
alert(content);
},1);
}
複製代碼
再來看下上面回調js的代碼:
- (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);
}];
}
複製代碼
一目瞭然了吧,這樣整個調用過程就結束了,固然window.webkit.messageHandlers.SHRMWKJSBridge.postMessage
和fetchComplete
後期咱們會把他們單獨封裝起來,對於前端開發者來講只會給他們統一的調用接口和回調接口,這個後面再說。若是說只是爲了簡單實現功能其實到這裏就能夠了,可是畢竟咱們是在構建一個框架,因此這樣是遠遠不行的,二篇就先到這吧(PS:主要是demo就寫到了這,另外時候不早了得睡了)。
總結一下,本篇簡單實現了native端的插件化,基於WKWebView通訊的構建,框架內不提供webView的實現三個功能這與咱們上一篇的預期也是相符合的,可是遠遠不夠,那麼咱們後續第三篇再繼續。那麼下篇會着重介紹native端插件的可配置和js端接口的封裝(這點真是難爲了我這個未入門的前端了)。