JSPatch详解(JavaScript 模块)-defineClass详解
第一篇defineClass都已经粗粗的讲过一些,粗个毛,啥都没讲哈哈。
Class有啥东西,有XXX,XXX,XXXXX,xxxxx
X个鬼
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
上面的东西看到过一万遍了。然而并没什么卵用
Method
我本来想从Properties开始讲起的,后来发现这个Properties说起还要穿插Method的东西。method这玩意儿分两种,instanseMethod,classMethod.他们的实现其实是差不多的。也可以说是一样的。
回到之前那篇文档里面给大家看的各种怪癖的写法
defineClass('JPViewController', {
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")()
self.__c("navigationController")().__c("pushViewController_animated")(tableViewCtrl, YES)
}
})
算了太多了就写一个吧。我们可以看下这个method和classMethod是个什么东西,其实很多时候可能大家太关注这个东西怎么用而忘记了这个的本质是什么。这个其实就是个对象。或者其实IOS里面这东西是不是很像一个Dictionary?那么作者把函数名命名成这样也就不奇怪了。
global.defineClass = function(declaration, properties, instMethods, clsMethods)
instMethods和clsMethods,定义成对象就能有效的区分函数名和函数体,毕竟OC(Swift也是,以后就直接省略Swift了)的方法是没法让JS直接调用的。
接下来看看对 methods 的序列化
var _formatDefineMethods = function(methods, newMethods, realClsName) {
for (var methodName in methods) {
if (!(methods[methodName] instanceof Function)) return;
(function(){
var originMethod = methods[methodName]
newMethods[methodName] = [originMethod.length, function() {
try {
var args = _formatOCToJS(Array.prototype.slice.call(arguments))
var lastSelf = global.self
global.self = args[0]
if (global.self) global.self.__realClsName = realClsName
args.splice(0,1)
var ret = originMethod.apply(originMethod, args)
global.self = lastSelf
return ret
} catch(e) {
_OC_catch(e.message, e.stack)
}
}]
})()
}
}
看这么多肯定会一脸懵逼
var _formatDefineMethods = function(methods, newMethods, realClsName) {
for (var methodName in methods) {
if (!(methods[methodName] instanceof Function)) return;
(function(){...})()
}
}
这么看会好一些,首先遍历了所有的方法和方法名,然后分别methods序列化完成之后装进newMethods,就出来了一个对象
这之后呢就是调用OC的接口了。
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};
这里稍微提一下JSContext这个东西。在JSEngine启动的时候,初始化了一个JSContext对象,这个对象就是给予JavaScipt运行环境的,所以我们的JS就是跑在这个JSContext里面,这也是为什么JS能吊起JSContext定制好的Block,至于他的原理,下次再跟各位小伙伴讲吧。那么是defineClass
static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
{
NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];
NSString *className;
NSString *superClassName;
NSString *protocolNames;
[scanner scanUpToString:@":" intoString:&className];
if (!scanner.isAtEnd) {
scanner.scanLocation = scanner.scanLocation + 1;
[scanner scanUpToString:@"<" intoString:&superClassName];
if (!scanner.isAtEnd) {
scanner.scanLocation = scanner.scanLocation + 1;
[scanner scanUpToString:@">" intoString:&protocolNames];
}
}
if (!superClassName) superClassName = @"NSObject";
className = trim(className);
superClassName = trim(superClassName);
NSArray *protocols = [protocolNames length] ? [protocolNames componentsSeparatedByString:@","] : nil;
Class cls = NSClassFromString(className);
if (!cls) {
Class superCls = NSClassFromString(superClassName);
if (!superCls) {
_exceptionBlock([NSString stringWithFormat:@"can't find the super class %@", superClassName]);
return @{@"cls": className};
}
cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
objc_registerClassPair(cls);
}
if (protocols.count > 0) {
for (NSString* protocolName in protocols) {
Protocol *protocol = objc_getProtocol([trim(protocolName) cStringUsingEncoding:NSUTF8StringEncoding]);
class_addProtocol (cls, protocol);
}
}
for (int i = 0; i < 2; i ++) {
BOOL isInstance = i == 0;
JSValue *jsMethods = isInstance ? instanceMethods: classMethods;
Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);
NSDictionary *methodDict = [jsMethods toDictionary];
for (NSString *jsMethodName in methodDict.allKeys) {
JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
int numberOfArg = [jsMethodArr[0] toInt32];
NSString *selectorName = convertJPSelectorString(jsMethodName);
if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) {
selectorName = [selectorName stringByAppendingString:@":"];
}
JSValue *jsMethod = jsMethodArr[1];
if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {
overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);
} else {
BOOL overrided = NO;
for (NSString *protocolName in protocols) {
char *types = methodTypesInProtocol(protocolName, selectorName, isInstance, YES);
if (!types) types = methodTypesInProtocol(protocolName, selectorName, isInstance, NO);
if (types) {
overrideMethod(currCls, selectorName, jsMethod, !isInstance, types);
free(types);
overrided = YES;
break;
}
}
if (!overrided) {
if (![[jsMethodName substringToIndex:1] isEqualToString:@"_"]) {
NSMutableString *typeDescStr = [@"@@:" mutableCopy];
for (int i = 0; i < numberOfArg; i ++) {
[typeDescStr appendString:@"@"];
}
overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
}
}
}
class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");
return @{@"cls": className, @"superCls": superClassName};
}
我们暂且不关心他这个东西到底有啥用,只要先关心这里返回的是一个对象
{
"cls" : className,
"superCls" : superClassName
}
之后js就能获取这个值了
var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
var className = ret['cls']
var superCls = ret['superCls']
然后JS就把他的methods全部加到_ocCls对象中
if (superCls.length && _ocCls[superCls]) {
for (var funcName in _ocCls[superCls]['instMethods']) {
_ocCls[className]['instMethods'][funcName] = _ocCls[superCls]['instMethods'][funcName]
}
for (var funcName in _ocCls[superCls]['clsMethods']) {
_ocCls[className]['clsMethods'][funcName] = _ocCls[superCls]['clsMethods'][funcName]
}
}
_setupJSMethod(className, instMethods, 1, realClsName)
_setupJSMethod(className, clsMethods, 0, realClsName)
_setupJSMethod是这样的
var _setupJSMethod = function(className, methods, isInst, realClsName) {
for (var name in methods) {
var key = isInst ? 'instMethods': 'clsMethods',
func = methods[name]
_ocCls[className][key][name] = _wrapLocalMethod(name, func, realClsName)
}
}
实际上跟之前对superClass的操作是一样的。就是把方法加进去到_ocCls对象里面去了,截个他的demo的图给大家看一下
到这一步那么oc的一个Class,已经都已对象的形式存在ocCls对象里面了。就是可以直接在js里面直接调用到了。就像这样。
var tableViewCtrl = JPTableViewController.alloc().init()
这时候可能会有人会问,这个JPTableViewController明明在_ocCls里面
那我们先输出下global,看看里面有什么
很明显是有这个JPTableViewController,那么是什么时候加进去的呢。
return require(className)
在defineClass最后是有一个require,而require就会把className加到这个Global里面,到这位置已经跟上一章的内容连起来的。为什么能调用到alloc和init这里就不讲解了。
Properties
就是属性,上文已经提到了,就是如果你的属性不传他会帮你忽略了,就是略过啦,那么如果传了呢
if (properties) {
properties.forEach(function(name){
if (!instMethods[name]) {
instMethods[name] = _propertiesGetFun(name);
}
var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1);
if (!instMethods[nameOfSet]) {
instMethods[nameOfSet] = _propertiesSetFun(name);
}
});
}
这里很简单粗暴的生成了几个js实例方法-----instMethods。
setter和getter。
var _propertiesSetFun = function(name){
return function(jval){
var slf = this;
if (!slf.__ocProps) {
var props = _OC_getCustomProps(slf.__obj)
if (!props) {
props = {}
_OC_setCustomProps(slf.__obj, props)
}
slf.__ocProps = props;
}
slf.__ocProps[name] = jval;
};
}
这里要插一些内容,不然可能大家不太明白“__ocProps,__obj”这些东西具体的作用是啥?
JS无法直接使用使用OC对象这一点大家都认同吧。也就是实际上这个对象所有的实例方法都是由OC来调用的,JS完全没有能力
如果slf.__ocProps是空,就调用_OC_getCustomProps(),然后很多人就奇怪了,为什么JS里面没有这玩意儿,因为在OC里面,JSContext能支持把JS的方法吊起来OC的方法,这个JSContext,先不多扯。看官先别管这玩意儿。看下面
context[@"_OC_getCustomProps"] = ^id(JSValue *obj) {
id realObj = formatJSToOC(obj);
return objc_getAssociatedObject(realObj, kPropAssociatedObjectKey);
};
很多带"_OC_"这个字段的都是通过JSContext吊起OC的block。
这个realObj就是从JSValue转换过来的一个OC对象。然后就通过“objc_getAssociatedObject”,这边返回的是一个obj对象。这个obj对象就由JS储存起来。