LuaView SDK第二版设计插件化理解(一)

作者: 沧州宁少 | 来源:发表于2018-05-28 20:16 被阅读19次

    LuaView SDK第二版设计插件化理解(一)

    • 插件化设计前序。装饰设计模式的理解。

      • 装饰者模式的理解。即一种内容成为其他的内容的装饰。这个装饰可以无限嵌套。比如我现在想要一个巧克力 奶油 草莓 蛋糕。 草莓可以是奶油的装饰,奶油又是巧克力的装饰,奶油巧克力草莓又是蛋糕的装饰。这样内容的嵌套构成了一个完整的蛋糕。下面使用java代码演示下过程

        class Espreso implements  Beverage
        {
            Beverage beverage;
            public Espreso(Beverage beverage){
                this.beverage = beverage;
            }
            @Override
            public double cost() {
                if (this.beverage == null) return  1.8;
                return  1.8  + beverage.cost();
            }
        
            @Override
            public String descripiton() {
                return "Espreso";
            }
        }
        
        class  Trawberrys implements  Beverage
        {
            // 装饰器
            Beverage beverage;
            public Trawberrys(Beverage beverage){
                this.beverage = beverage;
            }
            @Override
            public double cost() {
                if (this.beverage == null) return  2.8;
                return 2.8 + beverage.cost();
            }
        
            @Override
            public String descripiton() {
                return "Trawberrys";
            }
        }
        
        class Cake implements  Beverage
        {
            // 装饰器
            Beverage beverage;
            public Cake(Beverage beverage){
                this.beverage = beverage;
            }
            @Override
            public double cost() {
                if (this.beverage == null) return  29;
                return 29 + beverage.cost();
            }
        
            @Override
            public String descripiton() {
                return "Cake";
            }
        }        
        

    在Main里面执行相应的装饰

    public class Main {
    
        public static void main(String[] args) {
    
            System.out.println("Hello World!");
    
            // 创建一个草莓对象
            Trawberrys tarwberrys = new Trawberrys(null);
            // 创建一个奶油对象。让草莓装饰它
            Espreso espreso  = new Espreso(tarwberrys);
            // 创建蛋糕对象,让奶油草莓装饰它
            Cake cake = new Cake(espreso);
            System.out.println(cake.cost());
        }
    }
    

    大致可以理解为装饰的嵌套,A可以成为B的内层包装,B又可以成为C的包装,对象通过接口的方式形成了隐形的关联。

    • 上面简单介绍了装饰者模式,后面解析下刘旭在项目中设计的插件化的模式。native对象通过接口的方式获取了一个插件的实例。所有的跟Lua虚拟机的交互都交给这个插件去完成。比如构造方法注册到Lua虚拟机、类中的实例方法注册到当前对象对应的元表,注册全局方法,注册静态方法,注册全局对象等。
      • 下面分析下插件的基层协议。
         /**
          基本插件协议
          */
         @protocol MILPluginProtocol <NSObject>
         
         @required
             // 插件的宿主类。遵循MILPluginExportProtocol协议的类对象
         @property (nonatomic, assign, readonly) Class<MILPluginExportProtocol> hostClass;
             // 插件可以获取LuaCore。进而获取Lua虚拟机
         @property (nonatomic, weak, readonly) LuaViewCore *luaCore;
             // 设置宿主类
         - (void)resetHostClass:(Class<MILPluginExportProtocol>)hostClass;
             // 设置LuaCore
         - (void)resetLuaCore:(LuaViewCore *)luaCore;
             // 注册方法到Lua虚拟机。注册的方法包括插件自身的和宿主类的
         - (void)registerHostClassToLua;
         
         @end
    
    • 上面提到的MILPluginExportProtocol 为可导出基类的协议。
        /**
         可导出协议
         */
        @protocol MILPluginExportProtocol <NSObject>
        
        @required
        + (id<MILPluginProtocol>)pluginOfLua;
        
        @end
    
    • 可导出的基类协议返回的是一个基类的插件对象。就也是通过导出基类获取基插件,去执行相应的注册。
    • 类插件的协议如下
          /**
           类插件协议
           */
          @protocol MILClassPluginProtocol <MILPluginProtocol>
          
          @required
          /**
           继承LUASDK中类时使用
           
           @param luaCore LuaViewCore对象
           */
              // 继承自LuaViewSDK的类通过 luaL_openlib(L, NULL, baseObjectFuncs, 0);原来SDK内部的注册方法进行注册
          - (void)openSDKSuperLibs:(LuaViewCore *)luaCore;
          //   这个方法暂时外部没有直接使用,在插件的内部通过宿主类拿到mm_lua_objc_class指针,然后调用这个方法进行注册。
          - (void)openlib:(LuaViewCore *)luaCore info:(const mm_lua_objc_class *)clazzInfo;
          
          // override
          @property (nonatomic, assign, readonly) Class<MILClassProtocol> hostClass;
          - (void)resetHostClass:(Class<MILClassProtocol>)hostClass;
          
          @end
    
    • 实体插件协议如下
        /**
         实体插件协议
         */
        @protocol MILEntityPluginProtocol <MILClassPluginProtocol>
        
        @required
            // 创建一个userData对象并压栈
        - (void)setup:(NSObject<MILEntityClassProtocol> *)obj;
            // userData和对lv_userData的引用智控
        - (void)dealloc4Lua;
        
        // override
        @property (nonatomic, assign, readonly) Class<MILEntityClassProtocol> hostClass;
        - (void)resetHostClass:(Class<MILEntityClassProtocol>)hostClass;
        
        @end
        
    
    • 可导出类协议如下
          /**
           可导出类协议
           */
          @protocol MILClassProtocol <MILPluginExportProtocol>
          
          @required
              // 返回一个结构体指针对象,内部包含了所有我们想要的信息。比如methodLists。
          + (const mm_lua_objc_class *)clazzInfo4Lua;
          
          // override
          + (id<MILClassPluginProtocol>)pluginOfLua;
          
          @end
    
    • 可导出实体类协议如下
          /**
           可导出实体类协议
           */
          @protocol MILEntityClassProtocol <MILClassProtocol>
          
          @required
          // override
          + (id<MILEntityPluginProtocol>)pluginOfLua;
          
          @optional
              //返回一个当前实体类关联到Lua虚拟机的userData对象
          - (LVUserDataInfo *)luaUserData;
          
          @end
    
    • 全局变量导出协议
          /**
           可导出全局变量协议
           */
          @protocol MILGlobalVariablesProtocol <MILPluginExportProtocol>
          
          @required
              // 具体的Value值
          + (id)globalVarMap4lua;
          // 在lua的名称(注册到lua Global表的名称)
          + (NSString *)nameInLua;
          
          @end
      
    

    具体插件设计

    • MILPlugin

      • 遵循MILPluginProtocol 拥有基础的hostClass和luaCore属性。完成了基类的设置luaCore和宿主类的任务。额外提供了一个(const char *)nameOfmetaTable:(const char *)name packageName:(const char *)pkg; 方法。可以返回元表的名称
    • MILObjectPlugin

      • 实体类的基类插件。MILPlugin的子类。
      • setup 通过classInfo4Lua获取一个mm_lua_objc_class结构体指针里面包含所有我们要的数据信息。然后生成一个userdata对象。获取元表设置元表到userData上。 这个方法的本质可以理解为LuaViewSDK中的构造方法。
      • registerHostClassToLua 真实的注册过程。先拿到宿主工程的classInfo4Lua获取宿主工程的所有的数据信息。然后通过内部的[MILExporter reg:self.luaCore.l clazz:classInfo->clz constructor:classInfo->constructor.mn cfunc:classInfo->constructor.func name:classInfo->l_clz]; 把构造方法注册到Lua虚拟机的Global表。即比如通过View 字符串可以在Lua环境拿到这个闭包,然后通过()的方式调用。lv_createClassMetaTable 创建元表并压栈,然后将SDK的一些Base方法注册到元表中。 再有就是通过[self openlib:self.luaCore info:classInfo]; 将上面提到的机构体指针传入。递归宿主类的父类,把methodList依次注册到栈顶的元表当中。
      • 上面提到的(void)openSDKSuperLibs:(LuaViewCore *)luaCore 会调用SDK内最通用的注册方法luaL_openlib(L, NULL, baseObjectFuncs, 0); 将方法写入元表。luaL_openLib的详解看第一版解释。
      • (void)openlib:(LuaViewCore *)luaCore info:(const mm_lua_objc_class *)clazzInfo 上面提到的openLib 方法会递归去执行向元表注册的过程,具体代码如下
            - (void)openlib:(LuaViewCore *)luaCore info:(const mm_lua_objc_class *)clazzInfo
            {
                NSAssert(luaCore.l, @"The lua state must not be nil!");
                lua_State *L = luaCore.l;
                if (mm_HasSuperClass(clazzInfo)) {
                    NSAssert(mm_CharPointIsNotNULL(clazzInfo->supreClz), @"The super class name must be nil!");
                    Class superClass = NSClassFromString([NSString stringWithUTF8String:clazzInfo->supreClz]);
                    NSAssert([superClass respondsToSelector:@selector(clazzInfo4Lua)], @"The -[%@ clazzInfo4Lua] method not found!",superClass);
                    [self openlib:luaCore info:[superClass clazzInfo4Lua]];
                }
                mm_lua_openlib(L, NULL, clazzInfo->methods, 0);
            }
            
      
    • MILViewPlugin

      • View对应的插件。重写了openSDKSuperLibs在这个方法里面调用了super的openSDKSuperLibs方法。然后又注册了LVView特有的一些列的funcs。这里提出一点小意见,感觉这里的话如果我设计会在ObjectPlugin留一个钩子,返回特定的数据。父类给空实现,子类有就调用,没有则忽略。父类非钩子的方法尽量不要重写,感觉稍微有点不优雅~ 。具体代码如下

        - (void)openSDKSuperLibs:(LuaViewCore *)luaCore
        {
            [super openSDKSuperLibs:luaCore];
            mm_luaExtendBaseView(luaCore);
        }
        
    • MILClassPlugin

      • 静态方法对应的插件。registerHostClassToLua 没有注册闭包到Lua虚拟机的过程。也没有创建元表的过程。仍然有[self openSDKSuperLibs:self.luaCore]; [self openlib:self.luaCore info:classInfo];的过程。其中openSDKSuperLibs为空实现。提供给外界的钩子方法。openlib 注册class->clz_methods方法到Global表中对应的当前类这张表中
    • MILGlobalVarPlugin

    • 注册全局对象到Lua虚拟机(具体到Global表)。内部最终[LVUtil defineGlobal:[clazz nameInLua] value:[clazz globalVarMap4lua] L:L]; 将全局对象注册到Lua虚拟机的global表中 +(void) defineGlobal:(NSString)globalName value:(id) value L:(lua_State)L 方法如下

      +(void) defineGlobal:(NSString*)globalName value:(id) value L:(lua_State*)L {
          if( globalName && value ) {
              lua_checkstack(L, 12);
              lv_pushNativeObject(L, value);
              lua_setglobal(L, globalName.UTF8String);
          } else {
              LVError(@"define Global Value");
          }
      }
      

      value 转native对象压栈,然后在全局表设置key对应的value为native objc。

    相关文章

      网友评论

        本文标题:LuaView SDK第二版设计插件化理解(一)

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