近日寫了一個好玩的庫JSONRnederKit
大概整個人處于空窗期吧馋贤,閑不下來,同時最近經(jīng)歷了一些事情畏陕,就讓寫代碼來填充自己配乓。
每次因?yàn)樾枨蟾耡pp,時間都非常長惠毁。比如說某個節(jié)日犹芹,想做些彩蛋,你可能就要更新版本了鞠绰。為了解決這個痛點(diǎn)羽莺,突發(fā)奇想,能不能用JSON 做一些簡單的單頁應(yīng)用呢洞豁,事實(shí)上是完全可以的。
截圖如下
核心文件
SSJSContext.m
,SSBaseRenderController.m
,NSObject+SSRende.m
,SSKit.js
,
文件 | 作用 |
---|---|
SSJSContext.m |
負(fù)責(zé)接收J(rèn)SON 生成新的容器View 返回給外界使用 |
SSBaseRenderController.m |
接收SSJSContext 返回的容器視圖并顯示 |
NSObject+SSRende.m |
一共一個方法荒给,調(diào)用OC 對象的任何方法 |
SSKit.js |
仿照UIKit丈挟,實(shí)現(xiàn)JS 的數(shù)據(jù)結(jié)構(gòu) |
SSTool.js |
提供字符串解析幫助 |
流程圖
st=>start: 獲取JSON
e=>end: 顯示結(jié)束
op1=>operation: SSJSContext提供JSON和wrapperView給JS
op2=>operation: JS 接收J(rèn)SON 開始解析
op3=>operation: 解析完畢,JS調(diào)用OC 生成視圖并設(shè)置各種屬性
op4=>operation: 設(shè)置完畢志电,通知jsContext曙咽,并返回wrapperView
op4=>operation: renderController 接收wrapperView
st->op1->op2->op3->op4->e
如何解析JSON
這里面肯定少不了OC和JS交互的,為了方便交互挑辆,我在SSJSContext
給JS
定義了oc_invokeWithArgs(ocObj,method,args);
這樣JS可以調(diào)用任意OC對象的任意方法例朱,同時定義了一系列和OC
對應(yīng)的類,繼承關(guān)系也對應(yīng)鱼蝉,例如:
NSObject
->NSObject
,
Controller
->UIViewController
,
View
->UIView
...
//JS調(diào)用該函數(shù)可以達(dá)到調(diào)用OC實(shí)例對象的方法洒嗤,其中JS傳遞的參數(shù)會自動轉(zhuǎn)化為OC相應(yīng)的類型
self[@"oc_invokeWithArgs"] = ^id(JSValue *ocPointer,
NSString *methodName,
JSValue *args){
id ocObj = [ocPointer toObject];
SEL methodSelector = NSSelectorFromString(methodName);
NSArray *oc_args = [args toArray];
//調(diào)用給NSObject添加的方法,可以調(diào)用OC實(shí)例對象的方法
id obj = [ocObj js_performSelector:methodSelector withObjects:oc_args];
return obj;
};
class NSObject{
constructor(){
//保存OC對象魁亦,相當(dāng)于強(qiáng)引用了指針
this.ocPointer = null;
//保存OC對象的類名渔隶,用于給OC反射創(chuàng)建一個OC實(shí)例
this.ocClsName = 'NSObject';
}
...
//創(chuàng)建OC對象
creatNative(){
//調(diào)用OC方法,創(chuàng)建實(shí)例洁奈,并保存
this.ocPointer = oc_creatObject(this.ocClsName.firstUpperCase());
}
...
//將JS對象綁定到OC對象
bindJSValueToOC(){
...
//執(zhí)行OC實(shí)例對象的相應(yīng)方法
oc_invokeWithOneJSArg(this.ocPointer,'setJsValue:',this);
}
//調(diào)用OC
invokeNative(method,...args){
return oc_invokeWithArgs(this.ocPointer,method,args);
}
}
視圖生成和層次關(guān)系解決
JS
里面接受JSON
傳遞給controller
實(shí)例间唉,調(diào)用controller
的produceSubviews
方法
produceSubviews(){
//this.components 就是獲取的JSON里面的components
if(!this.components) return;
this.components.forEach((item,index)=>{
//這里把ListView反射,生成ListView實(shí)例
let view = eval(`new ${item.type}()`);
//把單個單個component(item)交給view
view.initWithJSON(item);
this.wrapperView.addSubview(view);
this.viewStore.set(view.ocIdentify,view);
});
}
走到view.initWithJSON(item);
的時候利术,view
首先根據(jù)item
這個對象設(shè)置好自己的屬性呈野,例如ocClsName
等,再然后調(diào)用view.creatNative
創(chuàng)建一個OC
對象印叁,并自己保存在this.ocPointer
被冒,其次再遍歷item
里面的components
創(chuàng)建子視圖军掂,這就是一個遞歸調(diào)用,這樣就解決了視圖的層級關(guān)系姆打。再添加子視圖this.addSubview(view)
良姆,這個函數(shù)會調(diào)用OC
的方法。這樣就已經(jīng)布局好了視圖幔戏。
生成并布局好了視圖后玛追,JS
把wrapperView
交給SSBaseRenderController
來進(jìn)行顯示。
JSON字符串中的變量和函數(shù)處理
主要得益于ES6的模板字符串的設(shè)計
我怎么把 "`${UI.screenW}`"
變成"375"呢闲延,解決方法可能方法比較笨痊剖。
let string = "`${UI.screenW}`";
string = "return" + string;
value = (new Function(string)).call();
這樣 "`${UI.screenW}`" 就變成了"375"
這樣函數(shù)也不難處理了仍然是通過改字符串并new Function(string)
來解決。
由于JSON傳遞給JS后就直接變成了一個對象垒玲,這樣可以很容易對變量來進(jìn)行操作陆馁,也為數(shù)據(jù)流動的實(shí)現(xiàn)提供了可能。
數(shù)據(jù)的流動問題
難的是怎樣設(shè)計數(shù)據(jù)流動的形式合愈,我琢磨了很久叮贩。
最后決定使用“執(zhí)行Action”的形式來解決數(shù)據(jù)流動,參考了一下Redux佛析。把視圖變成一個狀態(tài)機(jī)益老,由狀態(tài)來決定視圖上面顯示的東西。
里面有很多細(xì)節(jié)處理寸莫,可以看源碼捺萌,有詳細(xì)的注釋,這里只是大致說一下原理膘茎。
聯(lián)系我
無論是否有疑問歡迎和我一起討論沒我會迅速回復(fù)你
地址:feelings0811@wutnew.net 或者 https://github.com/cx478815108