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
@required
// 插件的宿主类。遵循MILPluginExportProtocol协议的类对象
@property (nonatomic, assign, readonly) Class hostClass;
// 插件可以获取LuaCore。进而获取Lua虚拟机
@property (nonatomic, weak, readonly) LuaViewCore *luaCore;
// 设置宿主类
- (void)resetHostClass:(Class)hostClass;
// 设置LuaCore
- (void)resetLuaCore:(LuaViewCore *)luaCore;
// 注册方法到Lua虚拟机。注册的方法包括插件自身的和宿主类的
- (void)registerHostClassToLua;
@end
- 上面提到的MILPluginExportProtocol 为可导出基类的协议。
/**
可导出协议
*/
@protocol MILPluginExportProtocol
@required
+ (id)pluginOfLua;
@end
- 可导出的基类协议返回的是一个基类的插件对象。就也是通过导出基类获取基插件,去执行相应的注册。
- 类插件的协议如下
/**
类插件协议
*/
@protocol MILClassPluginProtocol
@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 hostClass;
- (void)resetHostClass:(Class)hostClass;
@end
- 实体插件协议如下
/**
实体插件协议
*/
@protocol MILEntityPluginProtocol
@required
// 创建一个userData对象并压栈
- (void)setup:(NSObject *)obj;
// userData和对lv_userData的引用智控
- (void)dealloc4Lua;
// override
@property (nonatomic, assign, readonly) Class hostClass;
- (void)resetHostClass:(Class)hostClass;
@end
- 可导出类协议如下
/**
可导出类协议
*/
@protocol MILClassProtocol
@required
// 返回一个结构体指针对象,内部包含了所有我们想要的信息。比如methodLists。
+ (const mm_lua_objc_class *)clazzInfo4Lua;
// override
+ (id)pluginOfLua;
@end
- 可导出实体类协议如下
/**
可导出实体类协议
*/
@protocol MILEntityClassProtocol
@required
// override
+ (id)pluginOfLua;
@optional
//返回一个当前实体类关联到Lua虚拟机的userData对象
- (LVUserDataInfo *)luaUserData;
@end
- 全局变量导出协议
/**
可导出全局变量协议
*/
@protocol MILGlobalVariablesProtocol
@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。