前段时间使用了JSPatch热跟新框架的APP都收到了Apple的警告信,开发者们在JSPatch的github项目主页上的Apple警告邮件 issue中展开了热烈的讨论。开发者bang590建议先不要使用JSPatch,现在时间已经过去了个把个月,在JSPatch跟新平台跟新公告中说他们已经提出了解决方案,使用JSPatch不会被Apple拒绝。易信项目也接入过JSPatch,修复了好几个线上Bug,但在使用过程也碰到了几个JSPatch的不足之处,遂萌生了自己实现一个热跟新框架的想法。
注意:目前由于LuaJIT的原因,只能在真机上运行,有编译过可以运行在模拟器上的LuaJIT的同学可以私下交流下
JSPatch使用JS作为开发语言,JavaScriptCore为JS何OC提供了方便的互通机制,但同时也存在着一些不足:
不支持64位整型数据
JS中的数值类型都存储为double,将OC中64位中整型数据传到JavaScriptCore中可能会导致精度损失。
JS代码无法并发执行
JSVirtualMachine文档中说avaScriptCore API是线程安全的,这个线程安全是通过JSVirtualMachine的全局锁来实现的,即JSVirtualMachine是不支持并发操作的。如果想要实现并发执行JS代码可以通过创建多个JSVirtualMachine的实例,但是JSValue是不能在JSVirtualMachine之间传递的。JSPatch虽然提供了GCD API但是JSVirtualMachine的限制从根本上决定了这些GCD API并不能实现并发执行JS代码。(就算JSPatch创建多个JSVirtualMachine实例来运行JS代码,也不能实现GCD的完整功能)
DynamOC选择Lua作为开发语言,但没有使用官方的实现,而是采用LuaJIT的实现。选择Lua语言和LuaJIT主要基于以下几点考虑:
在lua中:是关键字(表示调用的方法的第一个参数是调用者),所以OC方法中的:都要用_代替
local indexPath = runtime.NSIndexPath:indexPathForRow_inSection_(0, 1)
若OC原方法名中包含_,在lua中使用__代替
使用runtime.className获取OC中的class
local color = runtime.UIColor:redColor()
local view = runtime.UIView:alloc():init()
view:setNeedsLayout()
runtime.callSuper(instance, cmd, ...)
调用property的getter和setter方法来获取/设置property的值
view:setBackgroundColor_()
runtime.createClass(superClass, className, iVar, properties)
@param superClass: OC class,父类, runtime.className
@param className: 字符串,类名
@param iVar: 字典
{varName = "typeEncoding"}
@param properties: 字典
{ propertyName =
{
typeEncoding = "typeEncoding",
readOnly = true | false,
ownerShip = "weak|strong|copy",
nonatomic = true | false,
setterName = "setterName",
getterName = "getterName",
ivarName = "ivarName"
}
}
内置了一些常用的typeEncodeing:
runtime.encode.id = "@"
runtime.encode.Class = "#"
runtime.encode.SEL = ":"
runtime.encode.block = "@"
runetime.encode.void = "v"
runtime.encode.CGFloat
runtime.encode.NSInteger
runtime.encode.NSUInteger
runtime.encode.CGSize
runtime.encode.CGRect
runtime.encode.UIEdgeInsets
可以通过lua语言中的..字符串连接操作符自由组合
如果不设置ownerShip属性则默认为assign,如果不设置setterName,则默认为setPropertyName,如果不设置getterName,则默认为propertyName,如果不设自ivarName,则默认为_propertyName
runtime.addMethod(class, selector, lambda, methodEncoding)
@param class: OC class,runtime.className
@param selector: OC selector, runtime.SEL("selectorName")
@param lambda, lua function, 添加的方法定义
function(self, cmd, ...)
...
end
需要注意OC方法有两个隐藏参数,分别是第一个参数方法调用者和第二个参数调用的调用方法的SEL,所以methodEncoding要加上这两个隐藏参数,lambda定义的第一和第二各参数必须是self和cmd
@param methodEncoding: 字符串,方法的type encoding,例如"v@:",表示一个第一个参数是id,第二个参数是SEL,返回值为void的方法
如果添加的方法不存在则创建该方法,如果已存在则覆盖原方法
runtime.getIvar(instance, iVarName)
runtime.setIvar(instance, iVarName, value)
将lua function封装成OC Block,需要调用:
runtime.createBlock(lambda, methodEncoding)
array:enumerateObjectsUsingBlock_(runtime.createBlock(function(str, index, stop)
cocoa.NSLog(runtime.Obj("%@"), str)
end, runtime.encode.void..runtime.encode.id..runtime.encode.id..runtime.encode.NSUInteger.."^B"))
需要注意OC Block调用有一个隐藏参数,表示的是这个block方法的调用者,即这个block本身(类型为NSBlock *),所以methodEncoding要加上这个隐藏参数,跟方法定义不同,我们不会用到这个隐藏参数,所以lambda的定义不需要加上这个隐藏参数
local weakSelf = runtime.weak(self)
local strongSelf = runtime.strong(weakSelf)
local array = runtime.Obj({1, 2, 3})
local string = runtime.Obj("hello")
local dict = runtime.Obj({key = value})
local number = runtime.Obj(1)
目前只实现了dispatch_async和dispatch_sync
dispatch.async(dispatch.mainQueue, function()
...
end)
dispatch.async(dispatch.get_global_queue(0, 0), function()
...
end)
dispatch.sync(dispatch.mainQueue, function()
...
end)
dispatch.sync(dispatch.get_global_queue(0, 0), function()
...
end)
指针的typeEncoding为"^"加上所指类型的typeEncoding,对于特定的指针比如常见的BOOL 的typeEncoding为"^B",NSError\*的typeEncoding为"^@"
在lua中将指针理解为数组的首地址来访问存储所指向的值,比如:
function(str, index, stop)
...
stop[0] = true
end
stop的类型为BOOL *stop
本文来自网易实践者社区,经作者徐晖授权发布。