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

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。

    原文作者:沧州宁少
    原文地址: https://www.jianshu.com/p/97089e0e3344
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞