用JSON 实现iOS UI 实现原理说明

近日写了一个好玩的库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交互的,为了方便交互,我在SSJSContextJS 定义了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 实例,调用controllerproduceSubviews 方法

    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的方法。这样就已经布局好了视图。

生成并布局好了视图后,JSwrapperView交给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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容