一、基本使用
JSPatch 基于 JavaScriptCore
、Runtime
实现,可以使用 JavaScript 代码对原方法进行重写(覆盖)、新增方法等,实现对线上问题代码的修复,例如:
假设 JPViewController
中按钮点击事件如下:
1 2 3 4 5 6
| @implementation JPViewController
- (void)handleBtn:(id)sender { }
@end
|
现在要使用 JSPatch 覆盖该方法,使点击按钮跳转到 JPTableViewController
,其对应 JSPatch 热修复代码实现如下:
1 2 3 4 5 6
| defineClass('JPViewController', { handleBtn: function(sender) { var tableViewCtrl = JPTableViewController.alloc().init() self.navigationController().pushViewController_animated(tableViewCtrl, YES) } })
|
将上述热修复代码放到 demo.js 文件中,使用 JPEngine
执行 JS 代码即可实现对原方法的覆盖:
1 2 3 4
| [JPEngine startEngine]; NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"]; NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil]; [JPEngine evaluateScript:script];
|
在 JSPatch 的热修复代码中,调用 OC 方法的方式为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 调用类方法 var redColor = UIColor.redColor();
// 调用实例方法 var view = UIView.alloc().init(); view.setNeedsLayout();
// 多参数方法名使用 _ 分隔 self.navigationController().pushViewController_animated(tableViewCtrl, YES)
// 在方法名前加 ORIG 即可调用未覆盖前的 OC 原方法: defineClass("JPTableViewController", { viewDidLoad: function() { self.ORIGviewDidLoad(); }, })
|
二、实现原理
1、OC 方法的替换
前面热修复代码实例中,可以看到 defineClass 的调用:
1 2 3 4 5
| defineClass('JPViewController', { handleBtn: function(sender) { ... } })
|
在执行 defineClass
时,实际上会走进 JSPatch.js 中定义的 defineClass
中,defineClass
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| global.defineClass = function(declaration, properties, instMethods, clsMethods) { var newInstMethods = {}, newClsMethods = {} // 如果 properties 不是数组,则调整参数顺序 if (!(properties instanceof Array)) { clsMethods = instMethods instMethods = properties properties = null } // 如果有 properties,为每个属性生成 getter 和 setter 方法,并添加到 instMethods 中 if (properties) { // 遍历 properties 数组中的每一个属性名 properties.forEach(function(name){ // 如果 instMethods 中没有该属性名对应的方法,则为其生成 getter 方法 if (!instMethods[name]) { instMethods[name] = _propertiesGetFun(name); } var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1); // 如果 instMethods 中没有该 setter 方法,则为其生成 setter 方法 if (!instMethods[nameOfSet]) { instMethods[nameOfSet] = _propertiesSetFun(name); } }); } // 提取类名,并去除多余的空格 var realClsName = declaration.split(':')[0].trim() /** * instMethods/clsMethods -> newInstMethods/newClsMethods * newInstMethods/newClsMethods 都是数组,key 为原始函数的名称;value 为数组,[0] 为参数数量,[1] 函数实现 */ _formatDefineMethods(instMethods, newInstMethods, realClsName) _formatDefineMethods(clsMethods, newClsMethods, realClsName) // _OC_defineClass:新增方法,或者对原方法进行覆盖 var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods) var className = ret['cls'] var superCls = ret['superCls'] _ocCls[className] = { instMethods: {}, clsMethods: {}, } 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) return require(className) }
|
由上可知,defineClass
定义如下:
1
| defineClass(classDeclaration, [properties,] instanceMethods, classMethods)
|
classDeclaration
:字符串,类名/父类名和 Protocol
properties
:新增 property,字符串数组,可省略
instanceMethods
:要添加或覆盖的实例方法
classMethods
:要添加或覆盖的类方法
例如:
1 2 3 4 5 6 7
| defineClass("xxxClassName", ['property1', 'property2'], { instanceMethod1: function() {}, instanceMethod2: function() {} }, { classMethod1: function() {}, classMethod2: function() {} })
|
defineClass
中主要逻辑如下:
- 如果有
properties
,为每个属性生成 getter
和 setter
方法,并添加到 instMethods
中。
- 对
instMethods
、classMethods
进行包装处理,
- 包装前:
key
为原始函数的名称;value
为函数实现。
- 包装后:
key
为原始函数的名称;value
为数组,[0]
为参数数量,[1]
函数实现。
- 如果包装的原因是,从 JS 将数据传给 OC,OC 收到的是
JSValue
类型,OC 无法从中读取参数数量,而 OC 后续的逻辑需要使用参数数量。
- 执行
_OC_defineClass
,进行方法的新增和替换。
_OC_defineClass
是 OC 中通过 JSContext
注入的函数:
1 2 3
| context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) { return defineClass(classDeclaration, instanceMethods, classMethods); };
|
其会走进 OC 实现的 defineClass 方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
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}; }
|
简而言之,OC 中实现的 defineClass
主要逻辑如下:
- 解析出类、方法名、类型编码等信息
- 利用 Runtime 进行新增或替换方法
其中,方法的动态替换是通过其 overrideMethod
方法实现的,其具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription) { SEL selector = NSSelectorFromString(selectorName); if (!typeDescription) { Method method = class_getInstanceMethod(cls, selector); typeDescription = (char *)method_getTypeEncoding(method); } IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL; IMP msgForwardIMP = _objc_msgForward; #if !defined(__arm64__) if (typeDescription[0] == '{') { NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:typeDescription]; if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) { msgForwardIMP = (IMP)_objc_msgForward_stret; } } #endif
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) { IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@"); if (originalForwardImp) { class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@"); } }
[cls jp_fixMethodSignature]; if (class_respondsToSelector(cls, selector)) { NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName]; SEL originalSelector = NSSelectorFromString(originalSelectorName); if(!class_respondsToSelector(cls, originalSelector)) { class_addMethod(cls, originalSelector, originalImp, typeDescription); } } NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; _initJPOverideMethods(cls); _JSOverideMethods[cls][JPSelectorName] = function;
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription); }
|
overrideMethod
逻辑总结如下:
先替换 forwardInvocation:
实现,替换成 JPForwardInvocation
- 同时将原保存到
forwardInvocation:
实现保存到 ORIGforwardInvocation:
- 执行
JPForwardInvocation
时,如果没有找到对应 JS 方法,则走原始转发流程,执行 ORIGforwardInvocation:
添加 -ORIGXXXXX
和 -_JPXXXXX
两个方法,前者指向原来的 IMP 实现,后者是 JS 热修复代码中的新实现。
- 如果热修复代码需要调用原方法,可以在方法名前拼上 ORIG 去调用,因为这一步保存了方法原实现
-ORIGXXXXX
将原方法实现指向 _objc_msgForward
,使方法直接走消息转发流程。
在 JPForwardInvocation
里,获取 Invocation
参数,传给 -_JPXXXXX
方法并执行,实现了 JS 方法替换 OC 方法。
该流程可用下图表示:
2、JavaScriptCore 执行 OC 方法
还以前面的热修复代码为例:
1 2 3 4 5 6
| defineClass('JPViewController', { handleBtn: function(sender) { var tableViewCtrl = JPTableViewController.alloc().init() self.navigationController().pushViewController_animated(tableViewCtrl, YES) } })
|
上述 JS 实现的热修复代码中,有类似 alloc
、init
等 OC 方法的调用,而在 JS 中,是不存在这些方法的,如果想要 JS 代码执行不报错,就需要保证这些方法在 JS 中正常被调用。
实际上,在 JPEngine
中执行 JS 代码时,逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| + (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL { if (!script || ![JSContext class]) { _exceptionBlock(@"script is nil"); return nil; } [self startEngine]; if (!_regex) { _regex = [NSRegularExpression regularExpressionWithPattern:_regexStr options:0 error:nil]; }
NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{\n%@\n}catch(e){_OC_catch(e.message, e.stack)}})();", [_regex stringByReplacingMatchesInString:script options:0 range:NSMakeRange(0, script.length) withTemplate:_replaceStr]]; @try { if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) { return [_context evaluateScript:formatedScript withSourceURL:resourceURL]; } else { return [_context evaluateScript:formatedScript]; } } @catch (NSException *exception) { _exceptionBlock([NSString stringWithFormat:@"%@", exception]); } return nil; }
|
在执行 JS 代码之前,JSPatch 使用正则找到了所有方法的调用,将方法的调用改成 __c
函数的调用,并放到 try catch 中,例如前面热修复代码,经过这一步之后,会变成:
1 2 3 4 5 6 7 8
| ;(function(){try{ defineClass('JPViewController', { handleBtn: function(sender) { var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")() self.__c("navigationController")().__c("pushViewController_animated")(tableViewCtrl, YES) } }) }catch(e){_OC_catch(e.message, e.stack)}})();
|
JSPatch 通过正则把所有方法调用都改成调用 __c()
函数,再执行这个 JS 脚本,达到类似 OC/Lua/Ruby 等的消息转发的目的。
__c
函数实现方式:在 JSPatch.js 中,给 JS 对象基类 Object 加上 __c
成员,这样所有对象都可以调用到 __c
:
1 2 3 4 5 6 7 8
| Object.defineProperty(Object.prototype, '__c', {value: function(methodName) { if (!this.__obj && !this.__clsName) return this[methodName].bind(this); var self = this return function(){ var args = Array.prototype.slice.call(arguments) return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper) } }})
|
在上述的 _methodFunc()
中,把相关信息传给 OC,OC 再利用 Runtime 去调用相应方法,返回结果值。这样就达到了 JS 调用任意 OC 方法的目的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) { var selectorName = methodName if (!isPerformSelector) { methodName = methodName.replace(/__/g, "-") selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_") var marchArr = selectorName.match(/:/g) var numOfArgs = marchArr ? marchArr.length : 0 if (args.length > numOfArgs) { selectorName += ":" } } var ret = instance ? _OC_callI(instance, selectorName, args, isSuper): _OC_callC(clsName, selectorName, args) return _formatOCToJS(ret) }
|
在 _methodFunc()
中,通过 _OC_callI
/_OC_callC
调用具体 OC 方法,_OC_callI
/_OC_callC
会执行 OC 实现的 callSelector
,在 callSelector
中利用 Runtime 去调用相应方法,返回结果值给到 JS。这样就达到了 JS 调用任意 OC 方法的目的:
1 2 3 4 5 6
| context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { return callSelector(nil, selectorName, arguments, obj, isSuper); }; context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { return callSelector(className, selectorName, arguments, nil, NO); };
|
该过程总结如下:
- 通过正则替换方法的调用:在执行 JS 代码之前,JSPatch 使用正则匹配找到所有方法的调用,并将其替换为调用
__c
函数。
object.methodName(param1, param2);
-> object.__c("methodName")(param1, param2);
__c
函数的实现: 在 JSPatch.js 中,给 JS 对象基类 Object 添加 __c
成员函数,这样所有对象都可以调用到 __c
,在 __c
中会调用 _methodFunc
函数。
_methodFunc
函数:负责处理具体的方法调用信息,通过 _OC_callI
和 _OC_callC
传递方法调用信息。
_OC_callI
/_OC_callC
:JSEngine
注入的方法,实际调用的是 OC 实现的 callSelector
。
callSelector
:利用 Runtime
执行方法,并将返回值返回给 JS。
3、JS 与 OC 数据传递
还以前面示例为例:
1 2 3 4 5 6 7 8
| ;(function(){try{ defineClass('JPViewController', { handleBtn: function(sender) { var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")() self.__c("navigationController")().__c("pushViewController_animated")(tableViewCtrl, YES) } }) }catch(e){_OC_catch(e.message, e.stack)}})();
|
上述实现有两个方向的数据传递:
OC -> JS
- JS 调用 OC ,获取 OC 返回值:
var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")()
tableViewCtrl
为 OC 返回给 JS 的数据。
JS -> OC
- JS 获取到 OC 返回值后,再调用 OC 方法转给 OC:
self.__c("navigationController")().__c("pushViewController_animated")(tableViewCtrl, YES)
tableViewCtrl
、YES
为 JS 传给 OC 的数据。
(1)OC -> JS 数据传递
前面已经提到,JS 的 __c
函数实际上是调用 _methodFunc()
,_methodFunc()
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) { var selectorName = methodName if (!isPerformSelector) { methodName = methodName.replace(/__/g, "-") selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_") var marchArr = selectorName.match(/:/g) var numOfArgs = marchArr ? marchArr.length : 0 if (args.length > numOfArgs) { selectorName += ":" } } var ret = instance ? _OC_callI(instance, selectorName, args, isSuper): _OC_callC(clsName, selectorName, args) return _formatOCToJS(ret) }
|
可以看到,_methodFunc()
中主要逻辑是获取获取具体方法调用信息以及具体参数,再去调用 _OC_callI
、_OC_callC
,_OC_callI
、_OC_callC
最终都会走进 OC 的 callSelector
中:
1 2 3 4 5 6 7
| context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { return callSelector(nil, selectorName, arguments, obj, isSuper); };
context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { return callSelector(className, selectorName, arguments, nil, NO); };
|
callSelector
逻辑伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper) { invocation = ... [invocation setTarget:xxxx]; id argumentsObj = formatJSToOC(arguments); NSMethodSignature *methodSignature = ......; NSUInteger numberOfArguments = methodSignature.numberOfArguments;
for (NSUInteger i = 2; i < numberOfArguments; i++) { const char *argumentType = [methodSignature getArgumentTypeAtIndex:i]; id valObj = argumentsObj[i-2]; value = covert(valObj); [invocation setArgument:&value atIndex:i]; } void *result; [invocation getReturnValue:&result];
return formatOCToJS(returnValue); }
|
可以看到,在 callSelector
中,执行完 OC 方法,会将 OC 方法返回值,调用 OC 中实现的 formatOCToJS
进行处理后再返回给 JS。
formatOCToJS
的作用就是将传入的 OC 对象包装成一个 NSDictionary
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| static id formatOCToJS(id obj) { if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSDictionary class]] || [obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDate class]]) { return _autoConvert ? obj: _wrapObj([JPBoxing boxObj:obj]); } if ([obj isKindOfClass:[NSNumber class]]) { return _convertOCNumberToString ? [(NSNumber*)obj stringValue] : obj; } if ([obj isKindOfClass:NSClassFromString(@"NSBlock")] || [obj isKindOfClass:[JSValue class]]) { return obj; }
return _wrapObj(obj); }
|
其中 _wrapObj
实现如下:
1 2 3 4 5 6 7
| static NSDictionary *_wrapObj(id obj) { if (!obj || obj == _nilObj) { return @{@"__isNil": @(YES)}; } return @{@"__obj": obj, @"__clsName": NSStringFromClass([obj isKindOfClass:[JPBoxing class]] ? [[((JPBoxing *)obj) unbox] class]: [obj class])}; }
|
字典中至少保存了两个信息:
这样 JS 中就可以通过判断对象是否有 __obj
属性得知这个对象是否表示 OC 对象指针。
该过程总结如下:
如果是 NSString
、NSDictionary
、NSArray
类型,则先将 obj
包装成 JPBoxing
,再调用 _wrapObj
将 JPBoxing
包装成字典。
NSMutableArray
/NSMutableDictionary
/NSMutableString
从 OC 传到 JS 时,JavaScriptCore
把它们转成了 JS 的 Array
/Object
/String
,再回传给 OC 时,OC 收到的不再是 NSMutableXXX,脱离了跟原对象的联系,导致在 JS 中无法调用三者可变对象中的方法去修改对象,解决办法是:
- 如果对象是 NSMutableXXX,就先将对象保存到
JPBoxing
,JS 拿到的是 JPBoxing
,从 JS 再传回给 OC 时就可以通过 JPBoxing
对象成员取到原始对象。
- 为了规则简单,JSPatch 让
NSArray
/NSDictionary
/NSString
也同样以封装的方式传递,避免在调用 OC 方法返回对象时还需要关心它返回的是可变还是不可变对象。
如果是其他普通 OC 对象,直接调用 _wrapObj
包装成字典。
JS 中通过判断对象是否有 __obj
属性得知这个对象是否表示 OC 对象指针。
- OC 的字典传到 JS,就变成 JS 的对象 Object 类型。
(2)JS -> OC 数据传递
1 2
| var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")() self.__c("navigationController")().__c("pushViewController_animated")(tableViewCtrl, YES)
|
上述 tableViewCtrl
是调用 JS 调用 OC 获取的返回值,即调用 _methodFunc
的返回值:
1 2 3 4 5 6 7 8
| var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
// 省略 ......
var ret = instance ? _OC_callI(instance, selectorName, args, isSuper): _OC_callC(clsName, selectorName, args) return _formatOCToJS(ret) }
|
_methodFunc
中,在获取到 OC 返回值的时候,会调用 JS 函数 _formatOCToJS
对返回值进行处理,JS 中的 _formatOCToJS
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var _formatOCToJS = function(obj) { if (obj === undefined || obj === null) return false
if (typeof obj == "object") { /** * OC 返回给 JS 的字典(OC 对象保存到字典的 __obj 中),到 JS 里就会自动变成 JS 的 Object * 如果有 __obj 标志,就代表是 OC 对象,取出 OC 对象返回 */ if (obj.__obj) return obj if (obj.__isNil) return false }
// 如果 obj 是一个数组,则递归地调用 _formatOCToJS 处理数组中的每一个元素,并返回新的数组。 if (obj instanceof Array) { var ret = [] obj.forEach(function(o) { ret.push(_formatOCToJS(o)) }) return ret }
// 省略 ...... }
|
可以看到,参数 tableViewCtrl
在传给 OC 之前,就已经从 OC 传递的字典中解析出原始对象,再将解析出原始对象传给 OC。
即:JS 传给 OC 的对象,是 OC 原始对象。
JS 与 OC 数据流转可用下图表示: