近日写了一个好玩的库JSONRnederKit
大概整个人处于空窗期吧,闲不下来,同时最近经历了一些事情,就让写代码来填充自己。
每次因为需求更新app,时间都非常长。比如说某个节日,想做些彩蛋,你可能就要更新版本了。为了解决这个痛点,突发奇想,能不能用JSON 做一些简单的单页应用呢,事实上是完全可以的。
截图如下
核心文件
SSJSContext.m
,SSBaseRenderController.m
,NSObject+SSRende.m
,SSKit.js
,
文件 | 作用 |
---|---|
SSJSContext.m |
负责接收JSON 生成新的容器View 返回给外界使用 |
SSBaseRenderController.m |
接收SSJSContext 返回的容器视图并显示 |
NSObject+SSRende.m |
一共一个方法,调用OC 对象的任何方法 |
SSKit.js |
仿照UIKit,实现JS 的数据结构 |
SSTool.js |
提供字符串解析帮助 |
流程图
st=>start: 获取JSON
e=>end: 显示结束
op1=>operation: SSJSContext提供JSON和wrapperView给JS
op2=>operation: JS 接收JSON 开始解析
op3=>operation: 解析完毕,JS调用OC 生成视图并设置各种属性
op4=>operation: 设置完毕,通知jsContext,并返回wrapperView
op4=>operation: renderController 接收wrapperView
st->op1->op2->op3->op4->e
如何解析JSON
这里面肯定少不了OC和JS交互的,为了方便交互,我在SSJSContext
给JS
定义了oc_invokeWithArgs(ocObj,method,args);
这样JS可以调用任意OC对象的任意方法,同时定义了一系列和OC
对应的类,继承关系也对应,例如:
NSObject
->NSObject
,
Controller
->UIViewController
,
View
->UIView
...
//JS调用该函数可以达到调用OC实例对象的方法,其中JS传递的参数会自动转化为OC相应的类型
self[@"oc_invokeWithArgs"] = ^id(JSValue *ocPointer,
NSString *methodName,
JSValue *args){
id ocObj = [ocPointer toObject];
SEL methodSelector = NSSelectorFromString(methodName);
NSArray *oc_args = [args toArray];
//调用给NSObject添加的方法,可以调用OC实例对象的方法
id obj = [ocObj js_performSelector:methodSelector withObjects:oc_args];
return obj;
};
class NSObject{
constructor(){
//保存OC对象,相当于强引用了指针
this.ocPointer = null;
//保存OC对象的类名,用于给OC反射创建一个OC实例
this.ocClsName = 'NSObject';
}
...
//创建OC对象
creatNative(){
//调用OC方法,创建实例,并保存
this.ocPointer = oc_creatObject(this.ocClsName.firstUpperCase());
}
...
//将JS对象绑定到OC对象
bindJSValueToOC(){
...
//执行OC实例对象的相应方法
oc_invokeWithOneJSArg(this.ocPointer,'setJsValue:',this);
}
//调用OC
invokeNative(method,...args){
return oc_invokeWithArgs(this.ocPointer,method,args);
}
}
视图生成和层次关系解决
JS
里面接受JSON
传递给controller
实例,调用controller
的produceSubviews
方法
produceSubviews(){
//this.components 就是获取的JSON里面的components
if(!this.components) return;
this.components.forEach((item,index)=>{
//这里把ListView反射,生成ListView实例
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
首先根据item
这个对象设置好自己的属性,例如ocClsName
等,再然后调用view.creatNative
创建一个OC
对象,并自己保存在this.ocPointer
,其次再遍历item
里面的components
创建子视图,这就是一个递归调用,这样就解决了视图的层级关系。再添加子视图this.addSubview(view)
,这个函数会调用OC
的方法。这样就已经布局好了视图。
生成并布局好了视图后,JS
把wrapperView
交给SSBaseRenderController
来进行显示。
JSON字符串中的变量和函数处理
主要得益于ES6的模板字符串的设计
我怎么把 "`${UI.screenW}`"
变成"375"呢,解决方法可能方法比较笨。
let string = "`${UI.screenW}`";
string = "return" + string;
value = (new Function(string)).call();
这样 "`${UI.screenW}`" 就变成了"375"
这样函数也不难处理了仍然是通过改字符串并new Function(string)
来解决。
由于JSON传递给JS后就直接变成了一个对象,这样可以很容易对变量来进行操作,也为数据流动的实现提供了可能。
数据的流动问题
难的是怎样设计数据流动的形式,我琢磨了很久。
最后决定使用“执行Action”的形式来解决数据流动,参考了一下Redux。把视图变成一个状态机,由状态来决定视图上面显示的东西。
里面有很多细节处理,可以看源码,有详细的注释,这里只是大致说一下原理。
联系我
无论是否有疑问欢迎和我一起讨论没我会迅速回复你
地址:feelings0811@wutnew.net 或者 https://github.com/cx478815108