本文將介紹小程序的核心視圖層邏輯層分離架構(gòu),并通過iOS的代碼來模擬這種雙線程模型。
什么是小程序
小程序是一種新的移動應(yīng)用程序格式,是一種依賴Web技術(shù),但也集成了原生應(yīng)用程序功能的混合解決方案。
目前市面上小程序平臺微信、支付寶、百度、頭條、京東、凡泰 等;小程序一些特性有助于填補Web和原生平臺之間的鴻溝,因此小程序受到了一些超級應(yīng)用程序的歡迎。
- 它不需要安裝,支持熱更新。
- 具備多個Web視圖以提高性能。
- 它提供了一些通過原生路徑訪問操作系統(tǒng)功能(原生接口)或數(shù)據(jù)的機制。
- 它的內(nèi)容通常更值得信賴,因為應(yīng)用程序需要由平臺驗證。
小程序可以分發(fā)到多個小程序平臺(Web、原生應(yīng)用,甚至是OS)。這些平臺還為小程序提供了入口,幫助用戶輕松找到所需的應(yīng)用。
小程序核心功能
分離視圖層與邏輯層
在小程序中,視圖層通常與邏輯層分離。
視圖層View負責(zé)渲染小程序頁面,包括Web組件和原生組件渲染,可以將其視為混合渲染。例如,Web組件渲染可以由WebView處理,但WebView不支持某些Web組件渲染,或者是性能受限;小程序還依賴于某些原生組件,例如地圖、視頻等。
邏輯層Service是用主要用于執(zhí)行小程序的JS邏輯。主要負責(zé)小程序的事件處理、API調(diào)用和生命周期管理。擴展的原生功能通常來自宿主原生應(yīng)用程序或操作系統(tǒng),這些功能包括拍照、位置、藍牙、網(wǎng)絡(luò)狀態(tài)、文件處理、掃描、電話等。它們通過某些API調(diào)用。當小程序調(diào)用原生API時,它會將API調(diào)用傳遞給擴展的原生功能,以便通過JSBridge進一步處理,并通過JSBridge從擴展的原生功能獲取結(jié)果。Service為每個Render建立連接,傳輸需要渲染的數(shù)據(jù)以進一步處理。
如果事件由小程序頁面中的組件觸發(fā),則此頁面將向Service發(fā)送事件以進一步處理。同時,頁面將等待Service發(fā)送的數(shù)據(jù)來重新渲染小程序頁面。
渲染過程可被視為無狀態(tài),并且所有狀態(tài)都將存儲在Service中。
視圖層和邏輯層分離有很多好處:
方便多個小程序頁面之間的數(shù)據(jù)共享和交互。
在小程序的生命周期中具有相同的上下文可以為具備原生應(yīng)用程序開發(fā)背景的開發(fā)人員提供熟悉的編碼體驗。
Service和View的分離和并行實現(xiàn)可以防止JS執(zhí)行影響或減慢頁面渲染,這有助于提高渲染性能。
因為JS在Service層執(zhí)行,所以JS里面操作的DOM將不會對View層產(chǎn)生影響,所以小程序是不能操作DOM結(jié)構(gòu)的,這也就使得小程序的性能比傳統(tǒng)的H5更好。
小程序雙線程模型模擬
接下來我們將用iOS代碼來模擬上述的雙線程模型。首先我們來實現(xiàn)視圖層與邏輯層的數(shù)據(jù)通訊
如上圖所示,視圖層與邏輯層都分別通過JS Bridge的publish和subscribe來實現(xiàn)數(shù)據(jù)的收發(fā)。
// 首先訂閱數(shù)據(jù)回調(diào)
JSBridge.subscribe('PAGE_EVENT',?function?(params) {
?// ... 這里對返回的數(shù)據(jù)進行處理
})
// 向JS Bridge發(fā)布數(shù)據(jù)
// eventName: 用于標識事件名
// data: 為傳遞的數(shù)據(jù)
JSBridge.publish('PAGE_EVENT', {eventName:?'onTest',data: {}})
首先需要對WKWebView初始化,
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事件回調(diào)處理
// 執(zhí)行視圖層事件回調(diào)
- (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
// 視圖層JSBridge請求接收處理
- (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"]];
????}
}
視圖層代碼
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">我來自視圖層!</div><input?type="button"?value="調(diào)用JS邏輯層setData"?style="border-radius:15px;background:#ed0c50;border: #EDD70C;color: white;font-size: 14px; width: 80%;"?onclick="onTest();"?/>
邏輯層代碼
??// page 對像模擬
?var?Page = {
????????setData:?function(data) {
?????????????ServiceJSBridge.publish('PAGE_EVENT', {
???????????????????eventName:?'onPageDataChange',data:data
?????????????????})
????????},
????????methods: {
????????????onTest:?function() {
????????????????Page.setData({
?????????????????????????????title:?'我來自JS代碼更新'
?????????????????????????????})
????????????????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)
})
具體代碼請參考(https://github.com/finochat/myapplet)
其他說明:
凡泰極客希望把迄今為止只有互聯(lián)網(wǎng)巨頭們才擁有的小程序技術(shù)能力,以“白牌”(white label)方式賦能給傳統(tǒng)企業(yè)。
我們把“小程序運行時”實現(xiàn)成一個可私有化部署的iOS和Android版本的SDK,任何機構(gòu)的App均可以嵌入該組件而瞬間獲得運行小程序的能力;同時也提供了“小程序開放平臺”的解決方案,供任何機構(gòu)、行業(yè)組織運行自己的小程序生態(tài)、統(tǒng)一上下架管理自己以及合作伙伴們的業(yè)務(wù)場景。
與互聯(lián)網(wǎng)巨頭方案不一樣的是,我們的方案不僅更加開放(提供API接口,支持二次開發(fā)),也更聚焦行業(yè)在合規(guī)監(jiān)管方面的特殊行業(yè)訴求。目前已在多家銀行、保險、證券以及相關(guān)行業(yè)機構(gòu)落地,并能夠自有機房內(nèi)獨立部署運行。不僅如此,“凡泰小程序技術(shù)”語法結(jié)構(gòu)和微信小程序兼容,一次開發(fā),多處上架,可降低開發(fā)成本,提升研發(fā)效率。
凡泰小程序當前已經(jīng)開放了一鍵部署功能,現(xiàn)可免費體驗,只需5行代碼30分鐘,即可在您的應(yīng)用內(nèi)集成「小程序運行時」,點擊https://mp.finogeeks.com/#/experience進入官網(wǎng)通過手機號即可完成注冊體驗