美文网首页iOS Developer
用JSON 实现iOS UI 实现原理说明

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

作者: 半罐汽水 | 来源:发表于2017-08-04 16:57 被阅读941次

    近日写了一个好玩的库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

    相关文章

      网友评论

        本文标题:用JSON 实现iOS UI 实现原理说明

        本文链接:https://www.haomeiwen.com/subject/ehcblxtx.html