前言
作為一名iOS開發(fā)工程師辛友,App的動態(tài)化是一種趨勢速侈,畢竟需求的增多率寡,頻繁的提交版本、更新版本對用戶體驗上肯定會有影響倚搬。當(dāng)然動態(tài)化的方案有很多種:RN冶共,Weex,LuaView等。對于一個對H5捅僵、React 零基礎(chǔ)的小白家卖,我準(zhǔn)備還是從LuaView入手。最后還想說一句庙楚,沒想到在簡書寫的第一篇文章是關(guān)于LuaView的上荡。好吧,我承認(rèn)我比較懶馒闷!
什么是LuaView酪捡?
LuaView是一種運行在一個ViewController/Activity中,可以靈活加載Lua腳本纳账,并能夠按照Native的方式運行的一種面向業(yè)務(wù)的開發(fā)技術(shù)方案逛薇。
LuaViewSDK使用lua虛擬機進行腳本解析,通過構(gòu)建lua與native之間的一系列基礎(chǔ)bridge功能疏虫,從另一個角度實現(xiàn)了動態(tài)化的native能力永罚。
而對于為何選用Lua,其最大的優(yōu)勢就是:lua語法精煉直觀议薪,lua虛擬機輕量高效尤蛮,使用Native編程模式,Native開發(fā)人員容易上手斯议。
以上很不要臉的取自其官方文檔的描述:?https://alibaba.github.io/LuaViewSDK/guide.html
LuaViewSDK 是阿里開源的一個實現(xiàn)動態(tài)化方案的框架产捞。開源地址:?https://github.com/alibaba/LuaViewSDK
目前其SDK由阿里的一個團隊來維護。個人感覺推廣力沒有Weex高哼御。官方文檔也很久沒有更新了坯临。不過提供了一個官方技術(shù)交流群:539262083 。
LuaViewSDK的整體架構(gòu)
上圖是LuaViewSDK的架構(gòu):(由下往上)
Native? & Framework :表示了Android恋昼、iOS及其對應(yīng)的框架層看靠。
Lua Engine:即Lua虛擬機,Android對應(yīng)LuaJ液肌,iOS對應(yīng)LuaC挟炬。作為lua腳本和nati語言之間的橋梁,將lua腳本翻譯成native能夠識別的目標(biāo)語言嗦哆。
Lua-Native UI Lib:LuaView的核心組件谤祖。其實LuaView對Native的各種UI組件進行了再次封裝,并且注冊到了Lua環(huán)境中老速,Lua腳本可以直接創(chuàng)建和操作這些組件粥喜,來達到創(chuàng)建和控制Native組件。(其實查看SDK源碼橘券,會發(fā)現(xiàn)额湘,不僅封裝了UI組件卿吐,還有一些方法類,如Timer锋华,Gesture等)嗡官。
Script Manager:Lua腳本管理器,用于腳本的解壓供置、驗證谨湘、加解密、解壓縮等工作芥丧。
Security:Lua腳本的校驗工作(完整性和安全性的校驗)。
Lua Script & Lua UI Lib:Lua 業(yè)務(wù)腳本以及 Lua 層的 UI 庫坊罢。
LuaView的基本用法
LuaView
第一種方式续担,直接創(chuàng)建LuaView對象,添加到你想渲染的View上活孩,運行腳本進行界面渲染物遇。
//1、創(chuàng)建LuaView憾儒,LView為LuaView子類(SDK封裝的)
self.lv= [[LView alloc]initWithFrame:lvRect];
self.lv.viewController= self;
[self.view addSubview:self.lv];
//2. 加載并運行腳本
[self.lv runFile:scriptFileName];
....
//3询兴、LuaView對象被回收之前必須清理內(nèi)存
[luaview releaseLuaView];
第二種方式,創(chuàng)建LViewController的控制器對象起趾,其屬性 lv 就是一個LuaView 對象诗舰,故運行腳本一樣實現(xiàn)了界面渲染。其已經(jīng)做好了各種生命周期和內(nèi)存管理的處理训裆,所以不用主動去釋放眶根。
//1. 創(chuàng)建LuaView VC
LViewController*luaVC=[[LViewController alloc]init];
//2. 加載并運行腳本
[luaVC.lv runFile:scriptFileName];
此處遇到一坑:
由于我初次使用lua,對其語法不熟边琉,自己創(chuàng)建demo運行腳本時属百,用了別人寫的一個簡單demo的腳本:繪制一個label”湟蹋可是運行后族扰,發(fā)現(xiàn)沒報錯,但是也沒繪制定欧,界面白板渔呵,也沒用返回錯誤提示。百思不得其解忧额!最后對比了下別人 demo 和我的 demo 的 LuaViewSDK厘肮,發(fā)現(xiàn)版本不一致,別人的是 2.5.xx.x睦番,而我的版本是0.5.1(最新的)类茂。而原先 LuaViewSDK 語法和 lua 標(biāo)準(zhǔn)語法有區(qū)別 :‘.’ 和 ':' 互換了耍属。最新SDK支持的lua標(biāo)準(zhǔn)語法(冒號調(diào)用方法,點調(diào)用屬性)巩检,所以我用最新SDK 運行原來語法寫成的腳本厚骗,是有問題的,語法不一致兢哭。最新的SDK中领舰,LuaView 的子類 LView 有一屬性 changeGrammar(默認(rèn)為NO),設(shè)置為YES會進行語法轉(zhuǎn)換迟螺。若新SDK 運行老語法的lua腳本冲秽,則需要將此屬性設(shè)置為 YES 。
而由于我項目中既有自己使用lua標(biāo)準(zhǔn)語法寫的腳本矩父,也有從別人demo拷貝過來的老語法lua腳本锉桑。故我想當(dāng)然的講 changeGrammar 設(shè)置為 YES,結(jié)果發(fā)現(xiàn)老語法lua腳本正常渲染界面窍株,標(biāo)準(zhǔn)語法腳本卻渲染失敗民轴,沒有錯誤提示,白板球订。后來發(fā)現(xiàn) changeGrammar 設(shè)置為YES后裸,并非將lua語法轉(zhuǎn)換成標(biāo)準(zhǔn)語法,而是遍歷腳本后冒滩,將 ‘.’ 和 ':' 進行互換微驶,所以標(biāo)準(zhǔn)語法寫的lua腳本又被轉(zhuǎn)換了。
所以標(biāo)準(zhǔn)語法的lua腳本旦部,changeGrammar 千萬別設(shè)置為 YES祈搜。
LuaViewCore
LuaViewCore其實就是Lua的虛擬機,負責(zé)實現(xiàn)了Lua腳本到Native語言的映射士八。查看LuaView.h/m源碼容燕,會發(fā)現(xiàn)LuaView初始化時,會創(chuàng)建一個?LuaViewCore 的對象婚度,即一個?LuaView?對應(yīng)一個 LuaViewCore蘸秘。
當(dāng)業(yè)務(wù)需要要求一個頁面有多個子View都需要lua控制渲染時,若通過創(chuàng)建多個LuaView方式來渲染蝗茁,則會創(chuàng)建多個LuaViewCore醋虏,這樣或多或少會影響性能。那么如何實現(xiàn)共享一個Lua虛擬機哮翘,即共享LuaViewCore颈嚼,來渲染多個界面。
//1饭寺、初始化LuaViewCore
self.lvCore = [[LuaViewCore alloc]init];
//2阻课、運行腳本
[self.lvCore runFile:@”luaName.lua”];
//? ? [self.lvCore loadFile:@”luaName.lua”];
//3叫挟、調(diào)用腳本里的方法 topViewUI/bottomViewUI ,在指定的 self.topView/self.bottomView 進行UI渲染
//str:成功則返回nil限煞,失敗則返回失敗原因
NSString *str0 = [self.lvCore callLua:@"topViewUI" environment:self.topView args:nil];
NSLog(@"%@",str0?str0:@"topViewUI-sucessed");
NSString *str1 = [self.lvCore callLua:@"bottomViewUI" environment:self.bottomView args:nil];
NSLog(@"%@",str1?str1:@"bottomViewUI-sucessed");
對應(yīng)的腳本 luaName.lua 如下:
function topViewUI( )
aLabel = Label();
aLabel:text("aaaa");
aLabel:frame(0, 0, 100, 30);
end
function bottomViewUI()
aLabel = Label();
aLabel:text("cccc");
aLabel:frame(0, 0, 100, 30);
end
此處遇到一坑:
正如我前面所述抹恳,其官方文檔很久沒有更新,可能維護也很少署驻。其官方描述?LuaViewCore 用法是這樣的:LuaViewCore初始化后奋献,load 腳本,然后就可以調(diào)用腳本的方法旺上。但是按照這個流程瓶蚂,調(diào)用腳本方法,會返回錯誤信息“function is nil error”抚官,即方法找不到扬跋。原因是,在lua中凌节,方法的定義是放在腳本運行時的,而非編譯時洒试。故僅僅編譯腳本倍奢,是無法調(diào)用腳本方法的。正確的流程是:LuaViewCore初始化后垒棋,run 腳本卒煞,然后就可以調(diào)用腳本的方法。(此處已和其官方團隊聯(lián)系確認(rèn)叼架,是其文檔有誤)
Native自定義功能橋接
源碼解析
在實現(xiàn)自定義功能橋接到Lua層之前畔裕,首先要從源碼入手,了解LuaView是如何封裝Native控件乖订,并且注冊到Lua環(huán)境中扮饶,Lua腳本可以任意創(chuàng)建和操作的!
正如上面所言乍构,LuaView 初始化時甜无,會初始化一個 LuaViewCore,然后就沒有其他什么特別的代碼哥遮。LuaViewCore 對象作為Lua虛擬機岂丘,所以密碼就在他這里。LuaViewCore 的初始化方法如下:
myInit 方法實現(xiàn)了屬性的初值賦值等眠饮。關(guān)鍵在于 registeLibs 方法奥帘。
此方法將所有LuaViewSDK封裝的NativeUI進行了遍歷注冊到Lua環(huán)境中。
而LVClassProtocal 協(xié)議的 +(int) lvClassDefine:(lua_State *)L globalName:(NSString*) globalName 方法仪召,即每個封裝的UI類需要實現(xiàn)的寨蹋,完成類及其方法注冊到Lua環(huán)境松蒜。比如LVImage:
lua是一種嵌入式的語言,可以作為c的擴展钥庇,也可以用c來編寫模塊了擴展lua牍鞠。而在進行數(shù)據(jù)交互的時候,存在這這么一個棧评姨,這個棧的作用是存儲lua和c交互的參數(shù)难述,返回值等。如lua調(diào)用c函數(shù)并傳入?yún)?shù)吐句,所有參數(shù)會先壓入這個棧胁后,c函數(shù)執(zhí)行時從棧中獲取參數(shù),執(zhí)行完后嗦枢,也會把返回值壓入此棧攀芯,lua從此棧中獲取返回值。(我個人理解是這樣的文虏,如有誤侣诺,煩請指出)
所以,上面LVImage的注冊氧秘,首先是將類名和其初始化方法壓棧年鸳,通過 lua_setglobal 方法,將棧頂?shù)念惷秃瘮?shù)注冊到lua環(huán)境中丸相,并通過globalName(此處是 "Image")進行標(biāo)注搔确。如此lua腳本中就可以通過 Image() 來創(chuàng)建LVImage對象。
而下面的 luaL_Reg 結(jié)構(gòu)體灭忠,則包含了一組 keyStr -- 方法膳算。則是將這組函數(shù)注冊到Lua環(huán)境中,作為全局函數(shù)弛作。Lua腳本中LVImage對象就可以調(diào)用這些方法涕蜂。
以上就實現(xiàn)了一個Native控件注入到lua環(huán)境中進行使用。
現(xiàn)有LuaView控件的擴展
上面已經(jīng)解讀了LVImage是如何注冊到Lua環(huán)境中進行使用缆蝉。當(dāng)Lua腳本里setImage 設(shè)置圖片宇葱,傳入?yún)?shù)是url時,圖片是沒有顯示的刊头,查看LVImage的方法會發(fā)現(xiàn)黍瞧,因為LVImage 的 setWebImageUrl 是沒有實現(xiàn)的。故考慮通過繼承的方式擴展LVImage的功能原杂,讓其支持網(wǎng)絡(luò)圖片的加載印颤。
#import "XQImage.h"
#import "LVHeads.h"
#import <SDWebImage/UIImageView+WebCache.h>
@implementation XQImage
-(void) setWebImageUrl:(NSURL*) url finished:(LVLoadFinished) finished{
[self sd_setImageWithURL:url];
}
@end
XQImage 繼承自LVImage,并且重寫了父類的方法 -(void) setWebImageUrl:(NSURL*) url finished:(LVLoadFinished) finished穿肄。(此處采用SDWebImage進行圖片下載展示)
自定義子類實現(xiàn)了年局,但是Lua環(huán)境注冊的還是父類LVImage际看,故,lua腳本初始化Image()矢否,還是會初始化父類的實例仲闽,故無法調(diào)用到子類的圖片下載賦值方法。父類的注冊僵朗,是在LuaView初始化時赖欣,那么LuaView初始化后,需要將子類覆蓋父類注冊到lua環(huán)境中验庙,讓 globalName(“Image”)對應(yīng)的是子類:
self.lv[@"Image"] = [XQImage class];
如此后顶吮,lua腳本 Image() 創(chuàng)建的就是native的XQImage對象。由此實現(xiàn)網(wǎng)絡(luò)圖片的加載和顯示粪薛。
完全自定義類的橋接
上節(jié)通過繼承的方式擴展 LuaView 已封裝的UI控件悴了,并且覆蓋注冊到lua環(huán)境中。那么如何將自定義的一個類违寿,橋接到Lua環(huán)境中使用呢湃交?
正如前面源碼解析,了解了 LuaView封裝的NativeUI 是如何實現(xiàn)的藤巢,所以按部就班巡揍,照著這個邏輯實現(xiàn)自定義類的橋接。其中最關(guān)鍵的是實現(xiàn) LVClassProtocal 協(xié)議的方法 + (int)lvClassDefine:(lua_State *)L globalName:(NSString *)globalName; 來實現(xiàn)指定類及其初始化方法菌瘪,全局函數(shù)注冊到 Lua 環(huán)境中,供 Lua 腳本直接使用阱当。
+(int) lvClassDefine:(lua_State *)L globalName:(NSString*) globalName{
[LVUtil reg:L clas:self cfunc:lvNewItem globalName:globalName defaultName:@"XQItemLuaView"];
const struct luaL_Reg memberFunctions [] = {
{"image",? setIconImage},
{"title",? ? title},
{NULL, NULL}
};
lv_createClassMetaTable(L,META_TABLE_CustomView);
luaL_openlib(L, NULL, [LVBaseView baseMemberFunctions], 0);
luaL_openlib(L, NULL, memberFunctions, 0);
const char* keys[] = { "addView", NULL};// 移除多余API
lv_luaTableRemoveKeys(L, keys );
return 1;
}
XQItemLuaView 是我自定義的一個 UIView 的子類俏扩,遵循 LVProtocal, LVClassProtocal 協(xié)議。其上有一個 ImageView 和 Label弊添。C函數(shù) lvNewItem 用于初始化一個 XQItemLuaView 的對象录淡,setIconImage 用于根據(jù) Lua 腳本傳入的參數(shù),設(shè)置 iconImageView 的圖片展示油坝。 title 為根據(jù)Lua腳本傳入的參數(shù)字符串嫉戚,設(shè)置 titleLabel 的text。
LVClassProtocal 是一個靜態(tài)協(xié)議澈圈,源碼分析中可以看到彬檀,LuaView 加載其擴展類的時候,都是通過初始化 LuaViewCore 時瞬女,遍歷所有需要加載的類窍帝,調(diào)用其 + (int)lvClassDefine:(lua_State *)L globalName:(NSString *)globalName 方法,實現(xiàn)加載诽偷。
而完全自定義的類的加載坤学,最好不要去直接更改其 LuaViewCore 源碼疯坤。所以我創(chuàng)建了一個管理自定義類注冊的操作類 XQRegisterManager 。
#import "XQRegisterManager.h"
#import "XQItemLuaView.h"
@implementation XQRegisterManager
/**
自定義類的注冊管理
@param luaState 狀態(tài)機
*/
+(void)registerClassWithLuaState:(lua_State*)luaState{
[XQItemLuaView lvClassDefine:luaState globalName:@"XQItemLuaView"];
}
@end
在 LuaView/LuaViewController 初始化后深浮,去調(diào)用注冊自定義的類压怠。如:
self.lv = [[LView alloc] initWithFrame:lvRect];
[XQRegisterManager registerClassWithLuaState:self.lv.luaviewCore.l];
self.lvCore = [[LuaViewCore alloc]init];
[XQRegisterManager registerClassWithLuaState:self.lvCore.l];
總結(jié)
以上是我一周時間學(xué)習(xí)LuaView的記錄。從簡單運行一個 Lua腳本開始認(rèn)識這個SDK飞苇,到最后分析源碼菌瘫,來實現(xiàn)自定義類的橋接。下一步的目標(biāo)是玄柠,在此基礎(chǔ)上突梦,研究資源腳本下載實現(xiàn),SDK自帶的debuger工具類的使用羽利,以及當(dāng)腳本出錯或者下載失敗的降級處理(LuaViewSDK 沒有自帶降級處理宫患,所有運行失敗會有錯誤拋出,需要根據(jù)錯誤这弧,自行處理降級還是顯示失敗頁面)等娃闲。
如有紕漏,歡迎指出匾浪,謝謝皇帮!