// JPEngine.m // JSPatch // // Created by bang on 15/4/30. // Copyright (c) 2015 bang. All rights reserved. // #import "JPEngine.h" #import <objc/runtime.h> #import <objc/message.h> #if TARGET_OS_IPHONE #import <UIKit/UIApplication.h> #endif #if CGFLOAT_IS_DOUBLE #define CGFloatValue doubleValue #else #define CGFloatValue floatValue #endif @implementation JPBoxing #define JPBOXING_GEN(_name, _prop, _type) \ + (instancetype)_name:(_type)obj \ { \ JPBoxing *boxing = [[JPBoxing alloc] init]; \ boxing._prop = obj; \ return boxing; \ } JPBOXING_GEN(boxObj, obj, id) JPBOXING_GEN(boxPointer, pointer, void *) JPBOXING_GEN(boxClass, cls, Class) JPBOXING_GEN(boxWeakObj, weakObj, id) JPBOXING_GEN(boxAssignObj, assignObj, id) - (id)unbox { if (self.obj) return self.obj; if (self.weakObj) return self.weakObj; if (self.assignObj) return self.assignObj; if (self.cls) return self.cls; return self; } - (void *)unboxPointer { return self.pointer; } - (Class)unboxClass { return self.cls; } @end #pragma mark - Fix iOS7 NSInvocation fatal error // A fatal error of NSInvocation on iOS7.0. // A invocation return 0 when the return type is double/float. // http://stackoverflow.com/questions/19874502/nsinvocation-getreturnvalue-with-double-value-produces-0-unexpectedly typedef struct {double d;} JPDouble; typedef struct {float f;} JPFloat; static NSMethodSignature *fixSignature(NSMethodSignature *signature) { #if TARGET_OS_IPHONE #ifdef __LP64__ if (!signature) { return nil; } if ([[UIDevice currentDevice].systemVersion floatValue] < 7.09) { BOOL isReturnDouble = (strcmp([signature methodReturnType], "d") == 0); BOOL isReturnFloat = (strcmp([signature methodReturnType], "f") == 0); if (isReturnDouble || isReturnFloat) { NSMutableString *types = [NSMutableString stringWithFormat:@"%s@:", isReturnDouble ? @encode(JPDouble) : @encode(JPFloat)]; for (int i = 2; i < signature.numberOfArguments; i++) { const char *argType = [signature getArgumentTypeAtIndex:i]; [types appendFormat:@"%s", argType]; } signature = [NSMethodSignature signatureWithObjCTypes:[types UTF8String]]; } } #endif #endif return signature; } @interface NSObject (JPFix) - (NSMethodSignature *)jp_methodSignatureForSelector:(SEL)aSelector; + (void)jp_fixMethodSignature; @end @implementation NSObject (JPFix) const static void *JPFixedFlagKey = &JPFixedFlagKey; - (NSMethodSignature *)jp_methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [self jp_methodSignatureForSelector:aSelector]; return fixSignature(signature); } + (void)jp_fixMethodSignature { #if TARGET_OS_IPHONE #ifdef __LP64__ if ([[UIDevice currentDevice].systemVersion floatValue] < 7.1) { NSNumber *flag = objc_getAssociatedObject(self, JPFixedFlagKey); if (!flag.boolValue) { SEL originalSelector = @selector(methodSignatureForSelector:); SEL swizzledSelector = @selector(jp_methodSignatureForSelector:); Method originalMethod = class_getInstanceMethod(self, originalSelector); Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); BOOL didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } objc_setAssociatedObject(self, JPFixedFlagKey, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } #endif #endif } @end #pragma mark - static JSContext *_context; static NSString *_regexStr = @"(?<!\\\\)\\.\\s*(\\w+)\\s*\\("; static NSString *_replaceStr = @".__c(\"$1\")("; static NSRegularExpression* _regex; static NSObject *_nullObj; static NSObject *_nilObj; static NSMutableDictionary *_registeredStruct; static NSMutableDictionary *_currInvokeSuperClsName; static char *kPropAssociatedObjectKey; static BOOL _autoConvert; static BOOL _convertOCNumberToString; static NSString *_scriptRootDir; static NSMutableSet *_runnedScript; static NSMutableDictionary *_JSOverideMethods; static NSMutableDictionary *_TMPMemoryPool; static NSMutableDictionary *_propKeys; static NSMutableDictionary *_JSMethodSignatureCache; static NSLock *_JSMethodSignatureLock; static NSRecursiveLock *_JSMethodForwardCallLock; static NSMutableDictionary *_protocolTypeEncodeDict; static NSMutableArray *_pointersToRelease; #ifdef DEBUG static NSArray *_JSLastCallStack; #endif static void (^_exceptionBlock)(NSString *log) = ^void(NSString *log) { NSCAssert(NO, log); }; @implementation JPEngine #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" #pragma mark - APIS + (void)startEngine { if (![JSContext class] || _context) { return; } JSContext *context = [[JSContext alloc] init]; #ifdef DEBUG context[@"po"] = ^JSValue*(JSValue *obj) { id ocObject = formatJSToOC(obj); return [JSValue valueWithObject:[ocObject description] inContext:_context]; }; context[@"bt"] = ^JSValue*() { return [JSValue valueWithObject:_JSLastCallStack inContext:_context]; }; #endif context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) { return defineClass(classDeclaration, instanceMethods, classMethods); }; context[@"_OC_defineProtocol"] = ^(NSString *protocolDeclaration, JSValue *instProtocol, JSValue *clsProtocol) { return defineProtocol(protocolDeclaration, instProtocol,clsProtocol); }; 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); }; context[@"_OC_formatJSToOC"] = ^id(JSValue *obj) { return formatJSToOC(obj); }; context[@"_OC_formatOCToJS"] = ^id(JSValue *obj) { return formatOCToJS([obj toObject]); }; context[@"_OC_getCustomProps"] = ^id(JSValue *obj) { id realObj = formatJSToOC(obj); return objc_getAssociatedObject(realObj, kPropAssociatedObjectKey); }; context[@"_OC_setCustomProps"] = ^(JSValue *obj, JSValue *val) { id realObj = formatJSToOC(obj); objc_setAssociatedObject(realObj, kPropAssociatedObjectKey, val, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }; context[@"__weak"] = ^id(JSValue *jsval) { id obj = formatJSToOC(jsval); return [[JSContext currentContext][@"_formatOCToJS"] callWithArguments:@[formatOCToJS([JPBoxing boxWeakObj:obj])]]; }; context[@"__strong"] = ^id(JSValue *jsval) { id obj = formatJSToOC(jsval); return [[JSContext currentContext][@"_formatOCToJS"] callWithArguments:@[formatOCToJS(obj)]]; }; context[@"_OC_superClsName"] = ^(NSString *clsName) { Class cls = NSClassFromString(clsName); return NSStringFromClass([cls superclass]); }; context[@"autoConvertOCType"] = ^(BOOL autoConvert) { _autoConvert = autoConvert; }; context[@"convertOCNumberToString"] = ^(BOOL convertOCNumberToString) { _convertOCNumberToString = convertOCNumberToString; }; context[@"include"] = ^(NSString *filePath) { NSString *absolutePath = [_scriptRootDir stringByAppendingPathComponent:filePath]; if (!_runnedScript) { _runnedScript = [[NSMutableSet alloc] init]; } if (absolutePath && ![_runnedScript containsObject:absolutePath]) { [JPEngine _evaluateScriptWithPath:absolutePath]; [_runnedScript addObject:absolutePath]; } }; context[@"resourcePath"] = ^(NSString *filePath) { return [_scriptRootDir stringByAppendingPathComponent:filePath]; }; context[@"dispatch_after"] = ^(double time, JSValue *func) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [func callWithArguments:nil]; }); }; context[@"dispatch_async_main"] = ^(JSValue *func) { dispatch_async(dispatch_get_main_queue(), ^{ [func callWithArguments:nil]; }); }; context[@"dispatch_sync_main"] = ^(JSValue *func) { if ([NSThread currentThread].isMainThread) { [func callWithArguments:nil]; } else { dispatch_sync(dispatch_get_main_queue(), ^{ [func callWithArguments:nil]; }); } }; context[@"dispatch_async_global_queue"] = ^(JSValue *func) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [func callWithArguments:nil]; }); }; context[@"releaseTmpObj"] = ^void(JSValue *jsVal) { if ([[jsVal toObject] isKindOfClass:[NSDictionary class]]) { void *pointer = [(JPBoxing *)([jsVal toObject][@"__obj"]) unboxPointer]; id obj = *((__unsafe_unretained id *)pointer); @synchronized(_TMPMemoryPool) { [_TMPMemoryPool removeObjectForKey:[NSNumber numberWithInteger:[(NSObject*)obj hash]]]; } } }; context[@"_OC_log"] = ^() { NSArray *args = [JSContext currentArguments]; for (JSValue *jsVal in args) { id obj = formatJSToOC(jsVal); NSLog(@"JSPatch.log: %@", obj == _nilObj ? nil : (obj == _nullObj ? [NSNull null]: obj)); } }; context[@"_OC_catch"] = ^(JSValue *msg, JSValue *stack) { _exceptionBlock([NSString stringWithFormat:@"js exception, \nmsg: %@, \nstack: \n %@", [msg toObject], [stack toObject]]); }; context.exceptionHandler = ^(JSContext *con, JSValue *exception) { NSLog(@"%@", exception); _exceptionBlock([NSString stringWithFormat:@"js exception: %@", exception]); }; _nullObj = [[NSObject alloc] init]; context[@"_OC_null"] = formatOCToJS(_nullObj); _context = context; _nilObj = [[NSObject alloc] init]; _JSMethodSignatureLock = [[NSLock alloc] init]; _JSMethodForwardCallLock = [[NSRecursiveLock alloc] init]; _registeredStruct = [[NSMutableDictionary alloc] init]; _currInvokeSuperClsName = [[NSMutableDictionary alloc] init]; #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"JSPatch" ofType:@"js"]; if (!path) _exceptionBlock(@"can't find JSPatch.js"); NSString *jsCore = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path] encoding:NSUTF8StringEncoding]; if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) { [_context evaluateScript:jsCore withSourceURL:[NSURL URLWithString:@"JSPatch.js"]]; } else { [_context evaluateScript:jsCore]; } } + (JSValue *)evaluateScript:(NSString *)script { return [self _evaluateScript:script withSourceURL:[NSURL URLWithString:@"main.js"]]; } + (JSValue *)evaluateScriptWithPath:(NSString *)filePath { _scriptRootDir = [filePath stringByDeletingLastPathComponent]; return [self _evaluateScriptWithPath:filePath]; } + (JSValue *)_evaluateScriptWithPath:(NSString *)filePath { NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; return [self _evaluateScript:script withSourceURL:[NSURL URLWithString:[filePath lastPathComponent]]]; } + (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; } + (JSContext *)context { return _context; } + (void)addExtensions:(NSArray *)extensions { if (![JSContext class]) { return; } if (!_context) _exceptionBlock(@"please call [JPEngine startEngine]"); for (NSString *className in extensions) { Class extCls = NSClassFromString(className); [extCls main:_context]; } } + (void)defineStruct:(NSDictionary *)defineDict { @synchronized (_context) { [_registeredStruct setObject:defineDict forKey:defineDict[@"name"]]; } } + (void)handleMemoryWarning { [_JSMethodSignatureLock lock]; _JSMethodSignatureCache = nil; [_JSMethodSignatureLock unlock]; } + (void)handleException:(void (^)(NSString *msg))exceptionBlock { _exceptionBlock = [exceptionBlock copy]; } #pragma mark - Implements static const void *propKey(NSString *propName) { if (!_propKeys) _propKeys = [[NSMutableDictionary alloc] init]; id key = _propKeys[propName]; if (!key) { key = [propName copy]; [_propKeys setObject:key forKey:propName]; } return (__bridge const void *)(key); } static id getPropIMP(id slf, SEL selector, NSString *propName) { return objc_getAssociatedObject(slf, propKey(propName)); } static void setPropIMP(id slf, SEL selector, id val, NSString *propName) { objc_setAssociatedObject(slf, propKey(propName), val, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } static char *methodTypesInProtocol(NSString *protocolName, NSString *selectorName, BOOL isInstanceMethod, BOOL isRequired) { Protocol *protocol = objc_getProtocol([trim(protocolName) cStringUsingEncoding:NSUTF8StringEncoding]); unsigned int selCount = 0; struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, isRequired, isInstanceMethod, &selCount); for (int i = 0; i < selCount; i ++) { if ([selectorName isEqualToString:NSStringFromSelector(methods[i].name)]) { char *types = malloc(strlen(methods[i].types) + 1); strcpy(types, methods[i].types); free(methods); return types; } } free(methods); return NULL; } static void defineProtocol(NSString *protocolDeclaration, JSValue *instProtocol, JSValue *clsProtocol) { const char *protocolName = [protocolDeclaration UTF8String]; Protocol* newprotocol = objc_allocateProtocol(protocolName); if (newprotocol) { addGroupMethodsToProtocol(newprotocol, instProtocol, YES); addGroupMethodsToProtocol(newprotocol, clsProtocol, NO); objc_registerProtocol(newprotocol); } } static void addGroupMethodsToProtocol(Protocol* protocol,JSValue *groupMethods,BOOL isInstance) { NSDictionary *groupDic = [groupMethods toDictionary]; for (NSString *jpSelector in groupDic.allKeys) { NSDictionary *methodDict = groupDic[jpSelector]; NSString *paraString = methodDict[@"paramsType"]; NSString *returnString = methodDict[@"returnType"] && [methodDict[@"returnType"] length] > 0 ? methodDict[@"returnType"] : @"void"; NSString *typeEncode = methodDict[@"typeEncode"]; NSArray *argStrArr = [paraString componentsSeparatedByString:@","]; NSString *selectorName = convertJPSelectorString(jpSelector); if ([selectorName componentsSeparatedByString:@":"].count - 1 < argStrArr.count) { selectorName = [selectorName stringByAppendingString:@":"]; } if (typeEncode) { addMethodToProtocol(protocol, selectorName, typeEncode, isInstance); } else { if (!_protocolTypeEncodeDict) { _protocolTypeEncodeDict = [[NSMutableDictionary alloc] init]; #define JP_DEFINE_TYPE_ENCODE_CASE(_type) \ [_protocolTypeEncodeDict setObject:[NSString stringWithUTF8String:@encode(_type)] forKey:@#_type];\ JP_DEFINE_TYPE_ENCODE_CASE(id); JP_DEFINE_TYPE_ENCODE_CASE(BOOL); JP_DEFINE_TYPE_ENCODE_CASE(int); JP_DEFINE_TYPE_ENCODE_CASE(void); JP_DEFINE_TYPE_ENCODE_CASE(char); JP_DEFINE_TYPE_ENCODE_CASE(short); JP_DEFINE_TYPE_ENCODE_CASE(unsigned short); JP_DEFINE_TYPE_ENCODE_CASE(unsigned int); JP_DEFINE_TYPE_ENCODE_CASE(long); JP_DEFINE_TYPE_ENCODE_CASE(unsigned long); JP_DEFINE_TYPE_ENCODE_CASE(long long); JP_DEFINE_TYPE_ENCODE_CASE(float); JP_DEFINE_TYPE_ENCODE_CASE(double); JP_DEFINE_TYPE_ENCODE_CASE(CGFloat); JP_DEFINE_TYPE_ENCODE_CASE(CGSize); JP_DEFINE_TYPE_ENCODE_CASE(CGRect); JP_DEFINE_TYPE_ENCODE_CASE(CGPoint); JP_DEFINE_TYPE_ENCODE_CASE(CGVector); JP_DEFINE_TYPE_ENCODE_CASE(NSRange); JP_DEFINE_TYPE_ENCODE_CASE(NSInteger); JP_DEFINE_TYPE_ENCODE_CASE(Class); JP_DEFINE_TYPE_ENCODE_CASE(SEL); JP_DEFINE_TYPE_ENCODE_CASE(void*); #if TARGET_OS_IPHONE JP_DEFINE_TYPE_ENCODE_CASE(UIEdgeInsets); #else JP_DEFINE_TYPE_ENCODE_CASE(NSEdgeInsets); #endif [_protocolTypeEncodeDict setObject:@"@?" forKey:@"block"]; [_protocolTypeEncodeDict setObject:@"^@" forKey:@"id*"]; } NSString *returnEncode = _protocolTypeEncodeDict[returnString]; if (returnEncode.length > 0) { NSMutableString *encode = [returnEncode mutableCopy]; [encode appendString:@"@:"]; for (NSInteger i = 0; i < argStrArr.count; i++) { NSString *argStr = trim([argStrArr objectAtIndex:i]); NSString *argEncode = _protocolTypeEncodeDict[argStr]; if (!argEncode) { NSString *argClassName = trim([argStr stringByReplacingOccurrencesOfString:@"*" withString:@""]); if (NSClassFromString(argClassName) != NULL) { argEncode = @"@"; } else { _exceptionBlock([NSString stringWithFormat:@"unreconized type %@", argStr]); return; } } [encode appendString:argEncode]; } addMethodToProtocol(protocol, selectorName, encode, isInstance); } } } } static void addMethodToProtocol(Protocol* protocol, NSString *selectorName, NSString *typeencoding, BOOL isInstance) { SEL sel = NSSelectorFromString(selectorName); const char* type = [typeencoding UTF8String]; protocol_addMethodDescription(protocol, sel, type, YES, isInstance); } 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}; } static JSValue *getJSFunctionInObjectHierachy(id slf, NSString *selectorName) { Class cls = object_getClass(slf); if (_currInvokeSuperClsName[selectorName]) { cls = NSClassFromString(_currInvokeSuperClsName[selectorName]); selectorName = [selectorName stringByReplacingOccurrencesOfString:@"_JPSUPER_" withString:@"_JP"]; } JSValue *func = _JSOverideMethods[cls][selectorName]; while (!func) { cls = class_getSuperclass(cls); if (!cls) { return nil; } func = _JSOverideMethods[cls][selectorName]; } return func; } static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation) { #ifdef DEBUG _JSLastCallStack = [NSThread callStackSymbols]; #endif BOOL deallocFlag = NO; id slf = assignSlf; BOOL isBlock = [[assignSlf class] isSubclassOfClass : NSClassFromString(@"NSBlock")]; NSMethodSignature *methodSignature = [invocation methodSignature]; NSInteger numberOfArguments = [methodSignature numberOfArguments]; NSString *selectorName = isBlock ? @"" : NSStringFromSelector(invocation.selector); NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; JSValue *jsFunc = isBlock ? objc_getAssociatedObject(assignSlf, "_JSValue")[@"cb"] : getJSFunctionInObjectHierachy(slf, JPSelectorName); if (!jsFunc) { JPExecuteORIGForwardInvocation(slf, selector, invocation); return; } NSMutableArray *argList = [[NSMutableArray alloc] init]; if (!isBlock) { if ([slf class] == slf) { [argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]]; } else if ([selectorName isEqualToString:@"dealloc"]) { [argList addObject:[JPBoxing boxAssignObj:slf]]; deallocFlag = YES; } else { [argList addObject:[JPBoxing boxWeakObj:slf]]; } } for (NSUInteger i = isBlock ? 1 : 2; i < numberOfArguments; i++) { const char *argumentType = [methodSignature getArgumentTypeAtIndex:i]; switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) { #define JP_FWD_ARG_CASE(_typeChar, _type) \ case _typeChar: { \ _type arg; \ [invocation getArgument:&arg atIndex:i]; \ [argList addObject:@(arg)]; \ break; \ } JP_FWD_ARG_CASE('c', char) JP_FWD_ARG_CASE('C', unsigned char) JP_FWD_ARG_CASE('s', short) JP_FWD_ARG_CASE('S', unsigned short) JP_FWD_ARG_CASE('i', int) JP_FWD_ARG_CASE('I', unsigned int) JP_FWD_ARG_CASE('l', long) JP_FWD_ARG_CASE('L', unsigned long) JP_FWD_ARG_CASE('q', long long) JP_FWD_ARG_CASE('Q', unsigned long long) JP_FWD_ARG_CASE('f', float) JP_FWD_ARG_CASE('d', double) JP_FWD_ARG_CASE('B', BOOL) case '@': { __unsafe_unretained id arg; [invocation getArgument:&arg atIndex:i]; if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) { [argList addObject:(arg ? [arg copy]: _nilObj)]; } else { [argList addObject:(arg ? arg: _nilObj)]; } break; } case '{': { NSString *typeString = extractStructName([NSString stringWithUTF8String:argumentType]); #define JP_FWD_ARG_STRUCT(_type, _transFunc) \ if ([typeString rangeOfString:@#_type].location != NSNotFound) { \ _type arg; \ [invocation getArgument:&arg atIndex:i]; \ [argList addObject:[JSValue _transFunc:arg inContext:_context]]; \ break; \ } JP_FWD_ARG_STRUCT(CGRect, valueWithRect) JP_FWD_ARG_STRUCT(CGPoint, valueWithPoint) JP_FWD_ARG_STRUCT(CGSize, valueWithSize) JP_FWD_ARG_STRUCT(NSRange, valueWithRange) @synchronized (_context) { NSDictionary *structDefine = _registeredStruct[typeString]; if (structDefine) { size_t size = sizeOfStructTypes(structDefine[@"types"]); if (size) { void *ret = malloc(size); [invocation getArgument:ret atIndex:i]; NSDictionary *dict = getDictOfStruct(ret, structDefine); [argList addObject:[JSValue valueWithObject:dict inContext:_context]]; free(ret); break; } } } break; } case ':': { SEL selector; [invocation getArgument:&selector atIndex:i]; NSString *selectorName = NSStringFromSelector(selector); [argList addObject:(selectorName ? selectorName: _nilObj)]; break; } case '^': case '*': { void *arg; [invocation getArgument:&arg atIndex:i]; [argList addObject:[JPBoxing boxPointer:arg]]; break; } case '#': { Class arg; [invocation getArgument:&arg atIndex:i]; [argList addObject:[JPBoxing boxClass:arg]]; break; } default: { NSLog(@"error type %s", argumentType); break; } } } if (_currInvokeSuperClsName[selectorName]) { Class cls = NSClassFromString(_currInvokeSuperClsName[selectorName]); NSString *tmpSelectorName = [[selectorName stringByReplacingOccurrencesOfString:@"_JPSUPER_" withString:@"_JP"] stringByReplacingOccurrencesOfString:@"SUPER_" withString:@"_JP"]; if (!_JSOverideMethods[cls][tmpSelectorName]) { NSString *ORIGSelectorName = [selectorName stringByReplacingOccurrencesOfString:@"SUPER_" withString:@"ORIG"]; [argList removeObjectAtIndex:0]; id retObj = callSelector(_currInvokeSuperClsName[selectorName], ORIGSelectorName, [JSValue valueWithObject:argList inContext:_context], [JSValue valueWithObject:@{@"__obj": slf, @"__realClsName": @""} inContext:_context], NO); id __autoreleasing ret = formatJSToOC([JSValue valueWithObject:retObj inContext:_context]); [invocation setReturnValue:&ret]; return; } } NSArray *params = _formatOCToJSList(argList); char returnType[255]; strcpy(returnType, [methodSignature methodReturnType]); // Restore the return type if (strcmp(returnType, @encode(JPDouble)) == 0) { strcpy(returnType, @encode(double)); } if (strcmp(returnType, @encode(JPFloat)) == 0) { strcpy(returnType, @encode(float)); } switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) { #define JP_FWD_RET_CALL_JS \ JSValue *jsval; \ [_JSMethodForwardCallLock lock]; \ jsval = [jsFunc callWithArguments:params]; \ [_JSMethodForwardCallLock unlock]; \ while (![jsval isNull] && ![jsval isUndefined] && [jsval hasProperty:@"__isPerformInOC"]) { \ NSArray *args = nil; \ JSValue *cb = jsval[@"cb"]; \ if ([jsval hasProperty:@"sel"]) { \ id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : nil, [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : nil, NO); \ args = @[[_context[@"_formatOCToJS"] callWithArguments:callRet ? @[callRet] : _formatOCToJSList(@[_nilObj])]]; \ } \ [_JSMethodForwardCallLock lock]; \ jsval = [cb callWithArguments:args]; \ [_JSMethodForwardCallLock unlock]; \ } #define JP_FWD_RET_CASE_RET(_typeChar, _type, _retCode) \ case _typeChar : { \ JP_FWD_RET_CALL_JS \ _retCode \ [invocation setReturnValue:&ret];\ break; \ } #define JP_FWD_RET_CASE(_typeChar, _type, _typeSelector) \ JP_FWD_RET_CASE_RET(_typeChar, _type, _type ret = [[jsval toObject] _typeSelector];) \ #define JP_FWD_RET_CODE_ID \ id __autoreleasing ret = formatJSToOC(jsval); \ if (ret == _nilObj || \ ([ret isKindOfClass:[NSNumber class]] && strcmp([ret objCType], "c") == 0 && ![ret boolValue])) ret = nil; \ #define JP_FWD_RET_CODE_POINTER \ void *ret; \ id obj = formatJSToOC(jsval); \ if ([obj isKindOfClass:[JPBoxing class]]) { \ ret = [((JPBoxing *)obj) unboxPointer]; \ } #define JP_FWD_RET_CODE_CLASS \ Class ret; \ ret = formatJSToOC(jsval); #define JP_FWD_RET_CODE_SEL \ SEL ret; \ id obj = formatJSToOC(jsval); \ if ([obj isKindOfClass:[NSString class]]) { \ ret = NSSelectorFromString(obj); \ } JP_FWD_RET_CASE_RET('@', id, JP_FWD_RET_CODE_ID) JP_FWD_RET_CASE_RET('^', void*, JP_FWD_RET_CODE_POINTER) JP_FWD_RET_CASE_RET('*', void*, JP_FWD_RET_CODE_POINTER) JP_FWD_RET_CASE_RET('#', Class, JP_FWD_RET_CODE_CLASS) JP_FWD_RET_CASE_RET(':', SEL, JP_FWD_RET_CODE_SEL) JP_FWD_RET_CASE('c', char, charValue) JP_FWD_RET_CASE('C', unsigned char, unsignedCharValue) JP_FWD_RET_CASE('s', short, shortValue) JP_FWD_RET_CASE('S', unsigned short, unsignedShortValue) JP_FWD_RET_CASE('i', int, intValue) JP_FWD_RET_CASE('I', unsigned int, unsignedIntValue) JP_FWD_RET_CASE('l', long, longValue) JP_FWD_RET_CASE('L', unsigned long, unsignedLongValue) JP_FWD_RET_CASE('q', long long, longLongValue) JP_FWD_RET_CASE('Q', unsigned long long, unsignedLongLongValue) JP_FWD_RET_CASE('f', float, floatValue) JP_FWD_RET_CASE('d', double, doubleValue) JP_FWD_RET_CASE('B', BOOL, boolValue) case 'v': { JP_FWD_RET_CALL_JS break; } case '{': { NSString *typeString = extractStructName([NSString stringWithUTF8String:returnType]); #define JP_FWD_RET_STRUCT(_type, _funcSuffix) \ if ([typeString rangeOfString:@#_type].location != NSNotFound) { \ JP_FWD_RET_CALL_JS \ _type ret = [jsval _funcSuffix]; \ [invocation setReturnValue:&ret];\ break; \ } JP_FWD_RET_STRUCT(CGRect, toRect) JP_FWD_RET_STRUCT(CGPoint, toPoint) JP_FWD_RET_STRUCT(CGSize, toSize) JP_FWD_RET_STRUCT(NSRange, toRange) @synchronized (_context) { NSDictionary *structDefine = _registeredStruct[typeString]; if (structDefine) { size_t size = sizeOfStructTypes(structDefine[@"types"]); JP_FWD_RET_CALL_JS void *ret = malloc(size); NSDictionary *dict = formatJSToOC(jsval); getStructDataWithDict(ret, dict, structDefine); [invocation setReturnValue:ret]; free(ret); } } break; } default: { break; } } if (_pointersToRelease) { for (NSValue *val in _pointersToRelease) { void *pointer = NULL; [val getValue:&pointer]; CFRelease(pointer); } _pointersToRelease = nil; } if (deallocFlag) { slf = nil; Class instClass = object_getClass(assignSlf); Method deallocMethod = class_getInstanceMethod(instClass, NSSelectorFromString(@"ORIGdealloc")); void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod); originalDealloc(assignSlf, NSSelectorFromString(@"dealloc")); } } static void JPExecuteORIGForwardInvocation(id slf, SEL selector, NSInvocation *invocation) { SEL origForwardSelector = @selector(ORIGforwardInvocation:); if ([slf respondsToSelector:origForwardSelector]) { NSMethodSignature *methodSignature = [slf methodSignatureForSelector:origForwardSelector]; if (!methodSignature) { _exceptionBlock([NSString stringWithFormat:@"unrecognized selector -ORIGforwardInvocation: for instance %@", slf]); return; } NSInvocation *forwardInv= [NSInvocation invocationWithMethodSignature:methodSignature]; [forwardInv setTarget:slf]; [forwardInv setSelector:origForwardSelector]; [forwardInv setArgument:&invocation atIndex:2]; [forwardInv invoke]; } else { Class superCls = [[slf class] superclass]; Method superForwardMethod = class_getInstanceMethod(superCls, @selector(forwardInvocation:)); void (*superForwardIMP)(id, SEL, NSInvocation *); superForwardIMP = (void (*)(id, SEL, NSInvocation *))method_getImplementation(superForwardMethod); superForwardIMP(slf, @selector(forwardInvocation:), invocation); } } static void _initJPOverideMethods(Class cls) { if (!_JSOverideMethods) { _JSOverideMethods = [[NSMutableDictionary alloc] init]; } if (!_JSOverideMethods[cls]) { _JSOverideMethods[(id<NSCopying>)cls] = [[NSMutableDictionary alloc] init]; } } 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] == '{') { //In some cases that returns struct, we should use the '_stret' API: //http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html //NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription. 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; // Replace the original selector at last, preventing threading issus when // the selector get called during the execution of `overrideMethod` class_replaceMethod(cls, selector, msgForwardIMP, typeDescription); } #pragma mark - static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper) { NSString *realClsName = [[instance valueForProperty:@"__realClsName"] toString]; if (instance) { instance = formatJSToOC(instance); if (class_isMetaClass(object_getClass(instance))) { className = NSStringFromClass((Class)instance); instance = nil; } else if (!instance || instance == _nilObj || [instance isKindOfClass:[JPBoxing class]]) { return @{@"__isNil": @(YES)}; } } id argumentsObj = formatJSToOC(arguments); if (instance && [selectorName isEqualToString:@"toJS"]) { if ([instance isKindOfClass:[NSString class]] || [instance isKindOfClass:[NSDictionary class]] || [instance isKindOfClass:[NSArray class]] || [instance isKindOfClass:[NSDate class]]) { return _unboxOCObjectToJS(instance); } } Class cls = instance ? [instance class] : NSClassFromString(className); SEL selector = NSSelectorFromString(selectorName); NSString *superClassName = nil; if (isSuper) { NSString *superSelectorName = [NSString stringWithFormat:@"SUPER_%@", selectorName]; SEL superSelector = NSSelectorFromString(superSelectorName); Class superCls; if (realClsName.length) { Class defineClass = NSClassFromString(realClsName); superCls = defineClass ? [defineClass superclass] : [cls superclass]; } else { superCls = [cls superclass]; } Method superMethod = class_getInstanceMethod(superCls, selector); IMP superIMP = method_getImplementation(superMethod); class_addMethod(cls, superSelector, superIMP, method_getTypeEncoding(superMethod)); NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; JSValue *overideFunction = _JSOverideMethods[superCls][JPSelectorName]; if (overideFunction) { overrideMethod(cls, superSelectorName, overideFunction, NO, NULL); } selector = superSelector; superClassName = NSStringFromClass(superCls); } NSMutableArray *_markArray; NSInvocation *invocation; NSMethodSignature *methodSignature; if (!_JSMethodSignatureCache) { _JSMethodSignatureCache = [[NSMutableDictionary alloc]init]; } if (instance) { [_JSMethodSignatureLock lock]; if (!_JSMethodSignatureCache[cls]) { _JSMethodSignatureCache[(id<NSCopying>)cls] = [[NSMutableDictionary alloc]init]; } methodSignature = _JSMethodSignatureCache[cls][selectorName]; if (!methodSignature) { methodSignature = [cls instanceMethodSignatureForSelector:selector]; methodSignature = fixSignature(methodSignature); _JSMethodSignatureCache[cls][selectorName] = methodSignature; } [_JSMethodSignatureLock unlock]; if (!methodSignature) { _exceptionBlock([NSString stringWithFormat:@"unrecognized selector %@ for instance %@", selectorName, instance]); return nil; } invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; [invocation setTarget:instance]; } else { methodSignature = [cls methodSignatureForSelector:selector]; methodSignature = fixSignature(methodSignature); if (!methodSignature) { _exceptionBlock([NSString stringWithFormat:@"unrecognized selector %@ for class %@", selectorName, className]); return nil; } invocation= [NSInvocation invocationWithMethodSignature:methodSignature]; [invocation setTarget:cls]; } [invocation setSelector:selector]; NSUInteger numberOfArguments = methodSignature.numberOfArguments; NSInteger inputArguments = [(NSArray *)argumentsObj count]; if (inputArguments > numberOfArguments - 2) { // calling variable argument method, only support parameter type `id` and return type `id` id sender = instance != nil ? instance : cls; id result = invokeVariableParameterMethod(argumentsObj, methodSignature, sender, selector); return formatOCToJS(result); } for (NSUInteger i = 2; i < numberOfArguments; i++) { const char *argumentType = [methodSignature getArgumentTypeAtIndex:i]; id valObj = argumentsObj[i-2]; switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) { #define JP_CALL_ARG_CASE(_typeString, _type, _selector) \ case _typeString: { \ _type value = [valObj _selector]; \ [invocation setArgument:&value atIndex:i];\ break; \ } JP_CALL_ARG_CASE('c', char, charValue) JP_CALL_ARG_CASE('C', unsigned char, unsignedCharValue) JP_CALL_ARG_CASE('s', short, shortValue) JP_CALL_ARG_CASE('S', unsigned short, unsignedShortValue) JP_CALL_ARG_CASE('i', int, intValue) JP_CALL_ARG_CASE('I', unsigned int, unsignedIntValue) JP_CALL_ARG_CASE('l', long, longValue) JP_CALL_ARG_CASE('L', unsigned long, unsignedLongValue) JP_CALL_ARG_CASE('q', long long, longLongValue) JP_CALL_ARG_CASE('Q', unsigned long long, unsignedLongLongValue) JP_CALL_ARG_CASE('f', float, floatValue) JP_CALL_ARG_CASE('d', double, doubleValue) JP_CALL_ARG_CASE('B', BOOL, boolValue) case ':': { SEL value = nil; if (valObj != _nilObj) { value = NSSelectorFromString(valObj); } [invocation setArgument:&value atIndex:i]; break; } case '{': { NSString *typeString = extractStructName([NSString stringWithUTF8String:argumentType]); JSValue *val = arguments[i-2]; #define JP_CALL_ARG_STRUCT(_type, _methodName) \ if ([typeString rangeOfString:@#_type].location != NSNotFound) { \ _type value = [val _methodName]; \ [invocation setArgument:&value atIndex:i]; \ break; \ } JP_CALL_ARG_STRUCT(CGRect, toRect) JP_CALL_ARG_STRUCT(CGPoint, toPoint) JP_CALL_ARG_STRUCT(CGSize, toSize) JP_CALL_ARG_STRUCT(NSRange, toRange) @synchronized (_context) { NSDictionary *structDefine = _registeredStruct[typeString]; if (structDefine) { size_t size = sizeOfStructTypes(structDefine[@"types"]); void *ret = malloc(size); getStructDataWithDict(ret, valObj, structDefine); [invocation setArgument:ret atIndex:i]; free(ret); break; } } break; } case '*': case '^': { if ([valObj isKindOfClass:[JPBoxing class]]) { void *value = [((JPBoxing *)valObj) unboxPointer]; if (argumentType[1] == '@') { if (!_TMPMemoryPool) { _TMPMemoryPool = [[NSMutableDictionary alloc] init]; } if (!_markArray) { _markArray = [[NSMutableArray alloc] init]; } memset(value, 0, sizeof(id)); [_markArray addObject:valObj]; } [invocation setArgument:&value atIndex:i]; break; } } case '#': { if ([valObj isKindOfClass:[JPBoxing class]]) { Class value = [((JPBoxing *)valObj) unboxClass]; [invocation setArgument:&value atIndex:i]; break; } } default: { if (valObj == _nullObj) { valObj = [NSNull null]; [invocation setArgument:&valObj atIndex:i]; break; } if (valObj == _nilObj || ([valObj isKindOfClass:[NSNumber class]] && strcmp([valObj objCType], "c") == 0 && ![valObj boolValue])) { valObj = nil; [invocation setArgument:&valObj atIndex:i]; break; } if ([(JSValue *)arguments[i-2] hasProperty:@"__isBlock"]) { JSValue *blkJSVal = arguments[i-2]; Class JPBlockClass = NSClassFromString(@"JPBlock"); if (JPBlockClass && ![blkJSVal[@"blockObj"] isUndefined]) { __autoreleasing id cb = [JPBlockClass performSelector:@selector(blockWithBlockObj:) withObject:[blkJSVal[@"blockObj"] toObject]]; [invocation setArgument:&cb atIndex:i]; Block_release((__bridge void *)cb); } else { __autoreleasing id cb = genCallbackBlock(arguments[i-2]); [invocation setArgument:&cb atIndex:i]; } } else { [invocation setArgument:&valObj atIndex:i]; } } } } if (superClassName) _currInvokeSuperClsName[selectorName] = superClassName; [invocation invoke]; if (superClassName) [_currInvokeSuperClsName removeObjectForKey:selectorName]; if ([_markArray count] > 0) { for (JPBoxing *box in _markArray) { void *pointer = [box unboxPointer]; id obj = *((__unsafe_unretained id *)pointer); if (obj) { @synchronized(_TMPMemoryPool) { [_TMPMemoryPool setObject:obj forKey:[NSNumber numberWithInteger:[(NSObject*)obj hash]]]; } } } } char returnType[255]; strcpy(returnType, [methodSignature methodReturnType]); // Restore the return type if (strcmp(returnType, @encode(JPDouble)) == 0) { strcpy(returnType, @encode(double)); } if (strcmp(returnType, @encode(JPFloat)) == 0) { strcpy(returnType, @encode(float)); } id returnValue; if (strncmp(returnType, "v", 1) != 0) { if (strncmp(returnType, "@", 1) == 0) { void *result; [invocation getReturnValue:&result]; //For performance, ignore the other methods prefix with alloc/new/copy/mutableCopy if ([selectorName isEqualToString:@"alloc"] || [selectorName isEqualToString:@"new"] || [selectorName isEqualToString:@"copy"] || [selectorName isEqualToString:@"mutableCopy"]) { returnValue = (__bridge_transfer id)result; } else { returnValue = (__bridge id)result; } return formatOCToJS(returnValue); } else { switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) { #define JP_CALL_RET_CASE(_typeString, _type) \ case _typeString: { \ _type tempResultSet; \ [invocation getReturnValue:&tempResultSet];\ returnValue = @(tempResultSet); \ break; \ } JP_CALL_RET_CASE('c', char) JP_CALL_RET_CASE('C', unsigned char) JP_CALL_RET_CASE('s', short) JP_CALL_RET_CASE('S', unsigned short) JP_CALL_RET_CASE('i', int) JP_CALL_RET_CASE('I', unsigned int) JP_CALL_RET_CASE('l', long) JP_CALL_RET_CASE('L', unsigned long) JP_CALL_RET_CASE('q', long long) JP_CALL_RET_CASE('Q', unsigned long long) JP_CALL_RET_CASE('f', float) JP_CALL_RET_CASE('d', double) JP_CALL_RET_CASE('B', BOOL) case '{': { NSString *typeString = extractStructName([NSString stringWithUTF8String:returnType]); #define JP_CALL_RET_STRUCT(_type, _methodName) \ if ([typeString rangeOfString:@#_type].location != NSNotFound) { \ _type result; \ [invocation getReturnValue:&result]; \ return [JSValue _methodName:result inContext:_context]; \ } JP_CALL_RET_STRUCT(CGRect, valueWithRect) JP_CALL_RET_STRUCT(CGPoint, valueWithPoint) JP_CALL_RET_STRUCT(CGSize, valueWithSize) JP_CALL_RET_STRUCT(NSRange, valueWithRange) @synchronized (_context) { NSDictionary *structDefine = _registeredStruct[typeString]; if (structDefine) { size_t size = sizeOfStructTypes(structDefine[@"types"]); void *ret = malloc(size); [invocation getReturnValue:ret]; NSDictionary *dict = getDictOfStruct(ret, structDefine); free(ret); return dict; } } break; } case '*': case '^': { void *result; [invocation getReturnValue:&result]; returnValue = formatOCToJS([JPBoxing boxPointer:result]); if (strncmp(returnType, "^{CG", 4) == 0) { if (!_pointersToRelease) { _pointersToRelease = [[NSMutableArray alloc] init]; } [_pointersToRelease addObject:[NSValue valueWithPointer:result]]; CFRetain(result); } break; } case '#': { Class result; [invocation getReturnValue:&result]; returnValue = formatOCToJS([JPBoxing boxClass:result]); break; } } return returnValue; } } return nil; } static id (*new_msgSend1)(id, SEL, id,...) = (id (*)(id, SEL, id,...)) objc_msgSend; static id (*new_msgSend2)(id, SEL, id, id,...) = (id (*)(id, SEL, id, id,...)) objc_msgSend; static id (*new_msgSend3)(id, SEL, id, id, id,...) = (id (*)(id, SEL, id, id, id,...)) objc_msgSend; static id (*new_msgSend4)(id, SEL, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id,...)) objc_msgSend; static id (*new_msgSend5)(id, SEL, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id,...)) objc_msgSend; static id (*new_msgSend6)(id, SEL, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id,...)) objc_msgSend; static id (*new_msgSend7)(id, SEL, id, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id,id,...)) objc_msgSend; static id (*new_msgSend8)(id, SEL, id, id, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id, id, id,...)) objc_msgSend; static id (*new_msgSend9)(id, SEL, id, id, id, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id, id, id, id, ...)) objc_msgSend; static id (*new_msgSend10)(id, SEL, id, id, id, id, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id, id, id, id, id,...)) objc_msgSend; static id invokeVariableParameterMethod(NSMutableArray *origArgumentsList, NSMethodSignature *methodSignature, id sender, SEL selector) { NSInteger inputArguments = [(NSArray *)origArgumentsList count]; NSUInteger numberOfArguments = methodSignature.numberOfArguments; NSMutableArray *argumentsList = [[NSMutableArray alloc] init]; for (NSUInteger j = 0; j < inputArguments; j++) { NSInteger index = MIN(j + 2, numberOfArguments - 1); const char *argumentType = [methodSignature getArgumentTypeAtIndex:index]; id valObj = origArgumentsList[j]; char argumentTypeChar = argumentType[0] == 'r' ? argumentType[1] : argumentType[0]; if (argumentTypeChar == '@') { [argumentsList addObject:valObj]; } else { return nil; } } id results = nil; numberOfArguments = numberOfArguments - 2; //If you want to debug the macro code below, replace it to the expanded code: //https://gist.github.com/bang590/ca3720ae1da594252a2e #define JP_G_ARG(_idx) getArgument(argumentsList[_idx]) #define JP_CALL_MSGSEND_ARG1(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0)); #define JP_CALL_MSGSEND_ARG2(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1)); #define JP_CALL_MSGSEND_ARG3(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2)); #define JP_CALL_MSGSEND_ARG4(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3)); #define JP_CALL_MSGSEND_ARG5(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4)); #define JP_CALL_MSGSEND_ARG6(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5)); #define JP_CALL_MSGSEND_ARG7(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6)); #define JP_CALL_MSGSEND_ARG8(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6), JP_G_ARG(7)); #define JP_CALL_MSGSEND_ARG9(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6), JP_G_ARG(7), JP_G_ARG(8)); #define JP_CALL_MSGSEND_ARG10(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6), JP_G_ARG(7), JP_G_ARG(8), JP_G_ARG(9)); #define JP_CALL_MSGSEND_ARG11(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6), JP_G_ARG(7), JP_G_ARG(8), JP_G_ARG(9), JP_G_ARG(10)); #define JP_IF_REAL_ARG_COUNT(_num) if([argumentsList count] == _num) #define JP_DEAL_MSGSEND(_realArgCount, _defineArgCount) \ if(numberOfArguments == _defineArgCount) { \ JP_CALL_MSGSEND_ARG##_realArgCount(_defineArgCount) \ } JP_IF_REAL_ARG_COUNT(1) { JP_CALL_MSGSEND_ARG1(1) } JP_IF_REAL_ARG_COUNT(2) { JP_DEAL_MSGSEND(2, 1) JP_DEAL_MSGSEND(2, 2) } JP_IF_REAL_ARG_COUNT(3) { JP_DEAL_MSGSEND(3, 1) JP_DEAL_MSGSEND(3, 2) JP_DEAL_MSGSEND(3, 3) } JP_IF_REAL_ARG_COUNT(4) { JP_DEAL_MSGSEND(4, 1) JP_DEAL_MSGSEND(4, 2) JP_DEAL_MSGSEND(4, 3) JP_DEAL_MSGSEND(4, 4) } JP_IF_REAL_ARG_COUNT(5) { JP_DEAL_MSGSEND(5, 1) JP_DEAL_MSGSEND(5, 2) JP_DEAL_MSGSEND(5, 3) JP_DEAL_MSGSEND(5, 4) JP_DEAL_MSGSEND(5, 5) } JP_IF_REAL_ARG_COUNT(6) { JP_DEAL_MSGSEND(6, 1) JP_DEAL_MSGSEND(6, 2) JP_DEAL_MSGSEND(6, 3) JP_DEAL_MSGSEND(6, 4) JP_DEAL_MSGSEND(6, 5) JP_DEAL_MSGSEND(6, 6) } JP_IF_REAL_ARG_COUNT(7) { JP_DEAL_MSGSEND(7, 1) JP_DEAL_MSGSEND(7, 2) JP_DEAL_MSGSEND(7, 3) JP_DEAL_MSGSEND(7, 4) JP_DEAL_MSGSEND(7, 5) JP_DEAL_MSGSEND(7, 6) JP_DEAL_MSGSEND(7, 7) } JP_IF_REAL_ARG_COUNT(8) { JP_DEAL_MSGSEND(8, 1) JP_DEAL_MSGSEND(8, 2) JP_DEAL_MSGSEND(8, 3) JP_DEAL_MSGSEND(8, 4) JP_DEAL_MSGSEND(8, 5) JP_DEAL_MSGSEND(8, 6) JP_DEAL_MSGSEND(8, 7) JP_DEAL_MSGSEND(8, 8) } JP_IF_REAL_ARG_COUNT(9) { JP_DEAL_MSGSEND(9, 1) JP_DEAL_MSGSEND(9, 2) JP_DEAL_MSGSEND(9, 3) JP_DEAL_MSGSEND(9, 4) JP_DEAL_MSGSEND(9, 5) JP_DEAL_MSGSEND(9, 6) JP_DEAL_MSGSEND(9, 7) JP_DEAL_MSGSEND(9, 8) JP_DEAL_MSGSEND(9, 9) } JP_IF_REAL_ARG_COUNT(10) { JP_DEAL_MSGSEND(10, 1) JP_DEAL_MSGSEND(10, 2) JP_DEAL_MSGSEND(10, 3) JP_DEAL_MSGSEND(10, 4) JP_DEAL_MSGSEND(10, 5) JP_DEAL_MSGSEND(10, 6) JP_DEAL_MSGSEND(10, 7) JP_DEAL_MSGSEND(10, 8) JP_DEAL_MSGSEND(10, 9) JP_DEAL_MSGSEND(10, 10) } return results; } NSMethodSignature *block_methodSignatureForSelector(id self, SEL _cmd, SEL aSelector) { uint8_t *p = (uint8_t *)((__bridge void *)self); p += sizeof(void *) * 2 + sizeof(int32_t) *2 + sizeof(uintptr_t) * 2; const char **signature = (const char **)p; return [NSMethodSignature signatureWithObjCTypes:*signature]; } static id getArgument(id valObj){ if (valObj == _nilObj || ([valObj isKindOfClass:[NSNumber class]] && strcmp([valObj objCType], "c") == 0 && ![valObj boolValue])) { return nil; } return valObj; } #pragma mark - static id genCallbackBlock(JSValue *jsVal) { void (^block)(void) = ^(void){}; uint8_t *p = (uint8_t *)((__bridge void *)block); p += sizeof(void *) + sizeof(int32_t) *2; void(**invoke)(void) = (void (**)(void))p; p += sizeof(void *) + sizeof(uintptr_t) * 2; const char **signature = (const char **)p; static NSMutableDictionary *typeSignatureDict; if (!typeSignatureDict) { typeSignatureDict = [NSMutableDictionary new]; #define JP_DEFINE_TYPE_SIGNATURE(_type) \ [typeSignatureDict setObject:@[[NSString stringWithUTF8String:@encode(_type)], @(sizeof(_type))] forKey:@#_type];\ JP_DEFINE_TYPE_SIGNATURE(id); JP_DEFINE_TYPE_SIGNATURE(BOOL); JP_DEFINE_TYPE_SIGNATURE(int); JP_DEFINE_TYPE_SIGNATURE(void); JP_DEFINE_TYPE_SIGNATURE(char); JP_DEFINE_TYPE_SIGNATURE(short); JP_DEFINE_TYPE_SIGNATURE(unsigned short); JP_DEFINE_TYPE_SIGNATURE(unsigned int); JP_DEFINE_TYPE_SIGNATURE(long); JP_DEFINE_TYPE_SIGNATURE(unsigned long); JP_DEFINE_TYPE_SIGNATURE(long long); JP_DEFINE_TYPE_SIGNATURE(unsigned long long); JP_DEFINE_TYPE_SIGNATURE(float); JP_DEFINE_TYPE_SIGNATURE(double); JP_DEFINE_TYPE_SIGNATURE(bool); JP_DEFINE_TYPE_SIGNATURE(size_t); JP_DEFINE_TYPE_SIGNATURE(CGFloat); JP_DEFINE_TYPE_SIGNATURE(CGSize); JP_DEFINE_TYPE_SIGNATURE(CGRect); JP_DEFINE_TYPE_SIGNATURE(CGPoint); JP_DEFINE_TYPE_SIGNATURE(CGVector); JP_DEFINE_TYPE_SIGNATURE(NSRange); JP_DEFINE_TYPE_SIGNATURE(NSInteger); JP_DEFINE_TYPE_SIGNATURE(Class); JP_DEFINE_TYPE_SIGNATURE(SEL); JP_DEFINE_TYPE_SIGNATURE(void*); JP_DEFINE_TYPE_SIGNATURE(void *); } NSString *types = [jsVal[@"args"] toString]; NSArray *lt = [types componentsSeparatedByString:@","]; NSString *funcSignature = @"@?0"; NSInteger size = sizeof(void *); for (NSInteger i = 1; i < lt.count;) { NSString *t = trim(lt[i]); NSString *tpe = typeSignatureDict[typeSignatureDict[t] ? t : @"id"][0]; if (i == 0) { funcSignature =[[NSString stringWithFormat:@"%@%@",tpe, [@(size) stringValue]] stringByAppendingString:funcSignature]; break; } funcSignature = [funcSignature stringByAppendingString:[NSString stringWithFormat:@"%@%@", tpe, [@(size) stringValue]]]; size += [typeSignatureDict[typeSignatureDict[t] ? t : @"id"][1] integerValue]; i = (i != lt.count - 1) ? i + 1 : 0; } IMP msgForwardIMP = _objc_msgForward; #if !defined(__arm64__) if ([funcSignature UTF8String][0] == '{') { //In some cases that returns struct, we should use the '_stret' API: //http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html //NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription. NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:[funcSignature UTF8String]]; if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) { msgForwardIMP = (IMP)_objc_msgForward_stret; } } #endif *invoke = (void *)msgForwardIMP; const char *fs = [funcSignature UTF8String]; char *s = malloc(strlen(fs)); strcpy(s, fs); *signature = s; objc_setAssociatedObject(block, "_JSValue", jsVal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class cls = NSClassFromString(@"NSBlock"); #define JP_HOOK_METHOD(selector, func) {Method method = class_getInstanceMethod([NSObject class], selector); \ BOOL success = class_addMethod(cls, selector, (IMP)func, method_getTypeEncoding(method)); \ if (!success) { class_replaceMethod(cls, selector, (IMP)func, method_getTypeEncoding(method));}} JP_HOOK_METHOD(@selector(methodSignatureForSelector:), block_methodSignatureForSelector); JP_HOOK_METHOD(@selector(forwardInvocation:), JPForwardInvocation); }); return block; } #pragma mark - Struct static int sizeOfStructTypes(NSString *structTypes) { const char *types = [structTypes cStringUsingEncoding:NSUTF8StringEncoding]; int index = 0; int size = 0; while (types[index]) { switch (types[index]) { #define JP_STRUCT_SIZE_CASE(_typeChar, _type) \ case _typeChar: \ size += sizeof(_type); \ break; JP_STRUCT_SIZE_CASE('c', char) JP_STRUCT_SIZE_CASE('C', unsigned char) JP_STRUCT_SIZE_CASE('s', short) JP_STRUCT_SIZE_CASE('S', unsigned short) JP_STRUCT_SIZE_CASE('i', int) JP_STRUCT_SIZE_CASE('I', unsigned int) JP_STRUCT_SIZE_CASE('l', long) JP_STRUCT_SIZE_CASE('L', unsigned long) JP_STRUCT_SIZE_CASE('q', long long) JP_STRUCT_SIZE_CASE('Q', unsigned long long) JP_STRUCT_SIZE_CASE('f', float) JP_STRUCT_SIZE_CASE('F', CGFloat) JP_STRUCT_SIZE_CASE('N', NSInteger) JP_STRUCT_SIZE_CASE('U', NSUInteger) JP_STRUCT_SIZE_CASE('d', double) JP_STRUCT_SIZE_CASE('B', BOOL) JP_STRUCT_SIZE_CASE('*', void *) JP_STRUCT_SIZE_CASE('^', void *) case '{': { NSString *structTypeStr = [structTypes substringFromIndex:index]; NSUInteger end = [structTypeStr rangeOfString:@"}"].location; if (end != NSNotFound) { NSString *subStructName = [structTypeStr substringWithRange:NSMakeRange(1, end - 1)]; NSDictionary *subStructDefine = [JPExtension registeredStruct][subStructName]; NSString *subStructTypes = subStructDefine[@"types"]; size += sizeOfStructTypes(subStructTypes); index += (int)end; break; } } default: break; } index ++; } return size; } static void getStructDataWithDict(void *structData, NSDictionary *dict, NSDictionary *structDefine) { NSArray *itemKeys = structDefine[@"keys"]; const char *structTypes = [structDefine[@"types"] cStringUsingEncoding:NSUTF8StringEncoding]; int position = 0; for (NSString *itemKey in itemKeys) { switch(*structTypes) { #define JP_STRUCT_DATA_CASE(_typeStr, _type, _transMethod) \ case _typeStr: { \ int size = sizeof(_type); \ _type val = [dict[itemKey] _transMethod]; \ memcpy(structData + position, &val, size); \ position += size; \ break; \ } JP_STRUCT_DATA_CASE('c', char, charValue) JP_STRUCT_DATA_CASE('C', unsigned char, unsignedCharValue) JP_STRUCT_DATA_CASE('s', short, shortValue) JP_STRUCT_DATA_CASE('S', unsigned short, unsignedShortValue) JP_STRUCT_DATA_CASE('i', int, intValue) JP_STRUCT_DATA_CASE('I', unsigned int, unsignedIntValue) JP_STRUCT_DATA_CASE('l', long, longValue) JP_STRUCT_DATA_CASE('L', unsigned long, unsignedLongValue) JP_STRUCT_DATA_CASE('q', long long, longLongValue) JP_STRUCT_DATA_CASE('Q', unsigned long long, unsignedLongLongValue) JP_STRUCT_DATA_CASE('f', float, floatValue) JP_STRUCT_DATA_CASE('F', CGFloat, CGFloatValue) JP_STRUCT_DATA_CASE('d', double, doubleValue) JP_STRUCT_DATA_CASE('B', BOOL, boolValue) JP_STRUCT_DATA_CASE('N', NSInteger, integerValue) JP_STRUCT_DATA_CASE('U', NSUInteger, unsignedIntegerValue) case '*': case '^': { int size = sizeof(void *); void *val = [(JPBoxing *)dict[itemKey] unboxPointer]; memcpy(structData + position, &val, size); break; } case '{': { NSString *subStructName = [NSString stringWithCString:structTypes encoding:NSASCIIStringEncoding]; NSUInteger end = [subStructName rangeOfString:@"}"].location; if (end != NSNotFound) { subStructName = [subStructName substringWithRange:NSMakeRange(1, end - 1)]; NSDictionary *subStructDefine = [JPExtension registeredStruct][subStructName]; NSDictionary *subDict = dict[itemKey]; int size = sizeOfStructTypes(subStructDefine[@"types"]); getStructDataWithDict(structData + position, subDict, subStructDefine); position += size; structTypes += end; break; } } default: break; } structTypes ++; } } static NSDictionary *getDictOfStruct(void *structData, NSDictionary *structDefine) { NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; NSArray *itemKeys = structDefine[@"keys"]; const char *structTypes = [structDefine[@"types"] cStringUsingEncoding:NSUTF8StringEncoding]; int position = 0; for (NSString *itemKey in itemKeys) { switch(*structTypes) { #define JP_STRUCT_DICT_CASE(_typeName, _type) \ case _typeName: { \ size_t size = sizeof(_type); \ _type *val = malloc(size); \ memcpy(val, structData + position, size); \ [dict setObject:@(*val) forKey:itemKey]; \ free(val); \ position += size; \ break; \ } JP_STRUCT_DICT_CASE('c', char) JP_STRUCT_DICT_CASE('C', unsigned char) JP_STRUCT_DICT_CASE('s', short) JP_STRUCT_DICT_CASE('S', unsigned short) JP_STRUCT_DICT_CASE('i', int) JP_STRUCT_DICT_CASE('I', unsigned int) JP_STRUCT_DICT_CASE('l', long) JP_STRUCT_DICT_CASE('L', unsigned long) JP_STRUCT_DICT_CASE('q', long long) JP_STRUCT_DICT_CASE('Q', unsigned long long) JP_STRUCT_DICT_CASE('f', float) JP_STRUCT_DICT_CASE('F', CGFloat) JP_STRUCT_DICT_CASE('N', NSInteger) JP_STRUCT_DICT_CASE('U', NSUInteger) JP_STRUCT_DICT_CASE('d', double) JP_STRUCT_DICT_CASE('B', BOOL) case '*': case '^': { size_t size = sizeof(void *); void *val = malloc(size); memcpy(val, structData + position, size); [dict setObject:[JPBoxing boxPointer:val] forKey:itemKey]; position += size; break; } case '{': { NSString *subStructName = [NSString stringWithCString:structTypes encoding:NSASCIIStringEncoding]; NSUInteger end = [subStructName rangeOfString:@"}"].location; if (end != NSNotFound) { subStructName = [subStructName substringWithRange:NSMakeRange(1, end - 1)]; NSDictionary *subStructDefine = [JPExtension registeredStruct][subStructName]; int size = sizeOfStructTypes(subStructDefine[@"types"]); NSDictionary *subDict = getDictOfStruct(structData + position, subStructDefine); [dict setObject:subDict forKey:itemKey]; position += size; structTypes += end; break; } } } structTypes ++; } return dict; } static NSString *extractStructName(NSString *typeEncodeString) { NSArray *array = [typeEncodeString componentsSeparatedByString:@"="]; NSString *typeString = array[0]; int firstValidIndex = 0; for (int i = 0; i< typeString.length; i++) { char c = [typeString characterAtIndex:i]; if (c == '{' || c=='_') { firstValidIndex++; }else { break; } } return [typeString substringFromIndex:firstValidIndex]; } #pragma mark - Utils static NSString *trim(NSString *string) { return [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } static BOOL blockTypeIsObject(NSString *typeString) { return [typeString rangeOfString:@"*"].location != NSNotFound || [typeString isEqualToString:@"id"]; } static BOOL blockTypeIsScalarPointer(NSString *typeString) { NSUInteger location = [typeString rangeOfString:@"*"].location; NSString *typeWithoutAsterisk = trim([typeString stringByReplacingOccurrencesOfString:@"*" withString:@""]); return (location == typeString.length-1 && !NSClassFromString(typeWithoutAsterisk)); } static NSString *convertJPSelectorString(NSString *selectorString) { NSString *tmpJSMethodName = [selectorString stringByReplacingOccurrencesOfString:@"__" withString:@"-"]; NSString *selectorName = [tmpJSMethodName stringByReplacingOccurrencesOfString:@"_" withString:@":"]; return [selectorName stringByReplacingOccurrencesOfString:@"-" withString:@"_"]; } #pragma mark - Object format 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); } static id formatJSToOC(JSValue *jsval) { id obj = [jsval toObject]; if (!obj || [obj isKindOfClass:[NSNull class]]) return _nilObj; if ([obj isKindOfClass:[JPBoxing class]]) return [obj unbox]; if ([obj isKindOfClass:[NSArray class]]) { NSMutableArray *newArr = [[NSMutableArray alloc] init]; for (int i = 0; i < [(NSArray*)obj count]; i ++) { [newArr addObject:formatJSToOC(jsval[i])]; } return newArr; } if ([obj isKindOfClass:[NSDictionary class]]) { if (obj[@"__obj"]) { id ocObj = [obj objectForKey:@"__obj"]; if ([ocObj isKindOfClass:[JPBoxing class]]) return [ocObj unbox]; return ocObj; } else if (obj[@"__clsName"]) { return NSClassFromString(obj[@"__clsName"]); } if (obj[@"__isBlock"]) { Class JPBlockClass = NSClassFromString(@"JPBlock"); if (JPBlockClass && ![jsval[@"blockObj"] isUndefined]) { return [JPBlockClass performSelector:@selector(blockWithBlockObj:) withObject:[jsval[@"blockObj"] toObject]]; } else { return genCallbackBlock(jsval); } } NSMutableDictionary *newDict = [[NSMutableDictionary alloc] init]; for (NSString *key in [obj allKeys]) { [newDict setObject:formatJSToOC(jsval[key]) forKey:key]; } return newDict; } return obj; } static id _formatOCToJSList(NSArray *list) { NSMutableArray *arr = [NSMutableArray new]; for (id obj in list) { [arr addObject:formatOCToJS(obj)]; } return arr; } 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])}; } static id _unboxOCObjectToJS(id obj) { if ([obj isKindOfClass:[NSArray class]]) { NSMutableArray *newArr = [[NSMutableArray alloc] init]; for (int i = 0; i < [(NSArray*)obj count]; i ++) { [newArr addObject:_unboxOCObjectToJS(obj[i])]; } return newArr; } if ([obj isKindOfClass:[NSDictionary class]]) { NSMutableDictionary *newDict = [[NSMutableDictionary alloc] init]; for (NSString *key in [obj allKeys]) { [newDict setObject:_unboxOCObjectToJS(obj[key]) forKey:key]; } return newDict; } if ([obj isKindOfClass:[NSString class]] ||[obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:NSClassFromString(@"NSBlock")] || [obj isKindOfClass:[NSDate class]]) { return obj; } return _wrapObj(obj); } #pragma clang diagnostic pop @end @implementation JPExtension + (void)main:(JSContext *)context{} + (void *)formatPointerJSToOC:(JSValue *)val { id obj = [val toObject]; if ([obj isKindOfClass:[NSDictionary class]]) { if (obj[@"__obj"] && [obj[@"__obj"] isKindOfClass:[JPBoxing class]]) { return [(JPBoxing *)(obj[@"__obj"]) unboxPointer]; } else { return NULL; } } else if (![val toBool]) { return NULL; } else{ return [((JPBoxing *)[val toObject]) unboxPointer]; } } + (id)formatRetainedCFTypeOCToJS:(CFTypeRef)CF_CONSUMED type { return formatOCToJS([JPBoxing boxPointer:(void *)type]); } + (id)formatPointerOCToJS:(void *)pointer { return formatOCToJS([JPBoxing boxPointer:pointer]); } + (id)formatJSToOC:(JSValue *)val { if (![val toBool]) { return nil; } return formatJSToOC(val); } + (id)formatOCToJS:(id)obj { JSContext *context = [JSContext currentContext] ? [JSContext currentContext]: _context; return [context[@"_formatOCToJS"] callWithArguments:@[formatOCToJS(obj)]]; } + (int)sizeOfStructTypes:(NSString *)structTypes { return sizeOfStructTypes(structTypes); } + (void)getStructDataWidthDict:(void *)structData dict:(NSDictionary *)dict structDefine:(NSDictionary *)structDefine { return getStructDataWithDict(structData, dict, structDefine); } + (NSDictionary *)getDictOfStruct:(void *)structData structDefine:(NSDictionary *)structDefine { return getDictOfStruct(structData, structDefine); } + (NSMutableDictionary *)registeredStruct { return _registeredStruct; } + (NSDictionary *)overideMethods { return _JSOverideMethods; } + (NSMutableSet *)includedScriptPaths { return _runnedScript; } @end