diff --git a/Demo/iOSDemo/JSPatchDemo.xcodeproj/project.pbxproj b/Demo/iOSDemo/JSPatchDemo.xcodeproj/project.pbxproj index 7ecf48c0..c17c5af7 100644 --- a/Demo/iOSDemo/JSPatchDemo.xcodeproj/project.pbxproj +++ b/Demo/iOSDemo/JSPatchDemo.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 2D6D12FD1B0B8CF20095A435 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2D6D12FA1B0B8CF20095A435 /* Images.xcassets */; }; 2D6D12FF1B0B8CF20095A435 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D6D12FC1B0B8CF20095A435 /* main.m */; }; 2D6D13011B0B8CFF0095A435 /* demo.js in Resources */ = {isa = PBXBuildFile; fileRef = 2D6D13001B0B8CFF0095A435 /* demo.js */; }; + 2DA3C42B206A1892005877CB /* newBlockTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DA3C42A206A1892005877CB /* newBlockTest.m */; }; + 2DA3C434206B5569005877CB /* newBlockTest.js in Resources */ = {isa = PBXBuildFile; fileRef = 2DA3C433206B5569005877CB /* newBlockTest.js */; }; 360BCF4E1D82DCFC00202977 /* JPDispatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 360BCF4D1D82DCFC00202977 /* JPDispatch.m */; }; 360BCF541D82EFCD00202977 /* JPProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 360BCF531D82EFCD00202977 /* JPProtocol.m */; }; 3613C3861C6329F300E915CB /* JPEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 3613C3611C6329F300E915CB /* JPEngine.m */; }; @@ -93,6 +95,9 @@ 2D6D12FB1B0B8CF20095A435 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2D6D12FC1B0B8CF20095A435 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 2D6D13001B0B8CFF0095A435 /* demo.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = demo.js; sourceTree = ""; }; + 2DA3C429206A1892005877CB /* newBlockTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = newBlockTest.h; sourceTree = ""; }; + 2DA3C42A206A1892005877CB /* newBlockTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = newBlockTest.m; sourceTree = ""; }; + 2DA3C433206B5569005877CB /* newBlockTest.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newBlockTest.js; sourceTree = ""; }; 360BCF4C1D82DCFC00202977 /* JPDispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JPDispatch.h; sourceTree = ""; }; 360BCF4D1D82DCFC00202977 /* JPDispatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JPDispatch.m; sourceTree = ""; }; 360BCF521D82EFCD00202977 /* JPProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JPProtocol.h; sourceTree = ""; }; @@ -473,6 +478,9 @@ 36CE19231CB3E709007D73AC /* JPPerformanceTest.h */, 36CE19241CB3E709007D73AC /* JPPerformanceTest.m */, 36CE19261CB3E72B007D73AC /* performanceTest.js */, + 2DA3C429206A1892005877CB /* newBlockTest.h */, + 2DA3C42A206A1892005877CB /* newBlockTest.m */, + 2DA3C433206B5569005877CB /* newBlockTest.js */, 6C72B7941C352BA80086C98D /* newProtocolTest.h */, 6C72B7951C352BA80086C98D /* newProtocolTest.m */, 6C9C0EE31C25A7C700FCAAC5 /* newProtocolTest.js */, @@ -603,6 +611,7 @@ E1B89EAE1B228818000645C2 /* multithreadTest.js in Resources */, 369492601CFEFE42003F44CA /* jsCFunctionTest.js in Resources */, 36CE19271CB3E72B007D73AC /* performanceTest.js in Resources */, + 2DA3C434206B5569005877CB /* newBlockTest.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -664,6 +673,7 @@ E1B89EA31B218986000645C2 /* JPInheritanceTestObjects.m in Sources */, 4F9EBFC91D50749200EC72C1 /* JPNumberTest.m in Sources */, DE94AE2B1AF246C000E461D4 /* JSPatchTests.m in Sources */, + 2DA3C42B206A1892005877CB /* newBlockTest.m in Sources */, 3694925F1CFEFE42003F44CA /* JPCFunctionTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Demo/iOSDemo/JSPatchTests/JPPerformanceTest.h b/Demo/iOSDemo/JSPatchTests/JPPerformanceTest.h index a95e6cbc..50cfdf45 100644 --- a/Demo/iOSDemo/JSPatchTests/JPPerformanceTest.h +++ b/Demo/iOSDemo/JSPatchTests/JPPerformanceTest.h @@ -24,4 +24,5 @@ - (void)testJSCallMallocJPMemory; - (void)testJSCallMallocJPCFunction; + @end diff --git a/Demo/iOSDemo/JSPatchTests/JPPerformanceTest.m b/Demo/iOSDemo/JSPatchTests/JPPerformanceTest.m index 69a25b9e..c5a8bf75 100644 --- a/Demo/iOSDemo/JSPatchTests/JPPerformanceTest.m +++ b/Demo/iOSDemo/JSPatchTests/JPPerformanceTest.m @@ -7,6 +7,7 @@ // #import "JPPerformanceTest.h" +#import @implementation JPPerformanceTest @@ -20,6 +21,7 @@ - (void)testJSCallJSMethodWithLargeDictionaryParam{} - (void)testJSCallJSMethodWithLargeDictionaryParamAutoConvert{} - (void)testJSCallJSMethodWithParam{} + - (void)testOCCallEmptyMethod { for (int i = 0; i < 10000; i ++) { [self emptyMethodToOverride]; @@ -49,7 +51,6 @@ - (void)initTestPerformanceObj { if (!testPerformanceObj) testPerformanceObj = [[NSObject alloc] init]; } - (void)emptyMethod { - } - (void)methodWithParamObject:(NSObject *)obj { @@ -69,4 +70,12 @@ - (NSObject *)methodReturnObjectToOverride { return nil; } +- (void)allArgSumWithBlock:(double (^)(CGFloat arg0, CGPoint arg1, NSInteger arg2, id arg3))block { + + NSNumber *arg3 = [NSNumber numberWithDouble:3.3]; + double sum = block(3.2, (CGPoint){1.1,1.2}, 10,arg3); + NSLog(@"==== sum = %@",@(sum)); +} + + @end diff --git a/Demo/iOSDemo/JSPatchTests/JSPatchTests.m b/Demo/iOSDemo/JSPatchTests/JSPatchTests.m index e41e5171..4af530a5 100644 --- a/Demo/iOSDemo/JSPatchTests/JSPatchTests.m +++ b/Demo/iOSDemo/JSPatchTests/JSPatchTests.m @@ -19,6 +19,7 @@ #import "JPPerformanceTest.h" #import "JPCFunctionTest.h" #import "JPNumberTest.h" +#import "newBlockTest.h" @interface JSPatchTests : XCTestCase @@ -479,6 +480,14 @@ - (void)testJSCallMallocJPCFunction }]; } +- (void)testNewBlock { + [self loadPatch:@"newBlockTest"]; + newBlockTest *obj = [[newBlockTest alloc] init]; + [obj removeJPBlock]; + [obj testJSBlockToOCCall]; + XCTAssert(obj.success, @"testJSBlockToOCCall"); +} + - (void)testNewProtocol{ [self loadPatch:@"newProtocolTest"]; diff --git a/Demo/iOSDemo/JSPatchTests/newBlockTest.h b/Demo/iOSDemo/JSPatchTests/newBlockTest.h new file mode 100644 index 00000000..183d09a2 --- /dev/null +++ b/Demo/iOSDemo/JSPatchTests/newBlockTest.h @@ -0,0 +1,20 @@ +// +// newBlockTest.h +// JSPatchTests +// +// Created by WELCommand on 2018/3/27. +// Copyright © 2018年 bang. All rights reserved. +// + +#import +#import + +@interface newBlockTest : NSObject + +@property (nonatomic, assign) BOOL success; + +- (void)removeJPBlock; + +- (void)testJSBlockToOCCall; + +@end diff --git a/Demo/iOSDemo/JSPatchTests/newBlockTest.js b/Demo/iOSDemo/JSPatchTests/newBlockTest.js new file mode 100644 index 00000000..b7b18937 --- /dev/null +++ b/Demo/iOSDemo/JSPatchTests/newBlockTest.js @@ -0,0 +1,7 @@ +defineClass("newBlockTest", { +testJSBlockToOCCall: function() { + self.performBlock(block("CGFloat, int, CGPoint, double, CGFloat, NSNumber*, NSString*, NSInteger", function(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { + return arg1 + arg2.x + arg2.y + arg3 + arg4 + arg5 + arg6.doubleValue() + arg7; + })); +} +}, {}); diff --git a/Demo/iOSDemo/JSPatchTests/newBlockTest.m b/Demo/iOSDemo/JSPatchTests/newBlockTest.m new file mode 100644 index 00000000..928d2a66 --- /dev/null +++ b/Demo/iOSDemo/JSPatchTests/newBlockTest.m @@ -0,0 +1,31 @@ +// +// newBlockTest.m +// JSPatchTests +// +// Created by WELCommand on 2018/3/27. +// Copyright © 2018年 bang. All rights reserved. +// + +#import "newBlockTest.h" +#import +#import "JPEngine.h" + +@implementation newBlockTest + +- (void)testJSBlockToOCCall {} + + ++ (void)main:(JSContext *)context +{ + context[@"__genBlock"] = nil; +} + +- (void)removeJPBlock { + [JPEngine addExtensions:@[@"newBlockTest"]]; +} + +- (void)performBlock:(CGFloat (^)(int arg1, CGPoint arg2, double arg3, CGFloat arg4, NSNumber *arg5, NSString *arg6, NSInteger arg7))block { + _success = (block(1, (CGPoint){3.3, 3.3}, 1.1, 1.1, @(11), @"4.4", 17) == (CGFloat)(1 + 3.3 + 3.3 + 1.1 + 1.1 + 11 + 4.4 + 17)) && (block(1, (CGPoint){3.3, 3.3}, 1.1, 1.1, @(11), @"4.4", 17) == (CGFloat)(1 + 3.3 + 3.3 + 1.1 + 1.1 + 11 + 4.4 + 17)); +} + +@end diff --git a/Demo/iOSDemo/JSPatchTests/performanceTest.js b/Demo/iOSDemo/JSPatchTests/performanceTest.js index dff3ea1b..51bacc74 100644 --- a/Demo/iOSDemo/JSPatchTests/performanceTest.js +++ b/Demo/iOSDemo/JSPatchTests/performanceTest.js @@ -157,5 +157,4 @@ defineClass('JPPerformanceTest', { var p = malloc(10) } } - -}) \ No newline at end of file +}) diff --git a/JSPatch/JPEngine.m b/JSPatch/JPEngine.m index 5c671b59..e260b14c 100644 --- a/JSPatch/JPEngine.m +++ b/JSPatch/JPEngine.m @@ -656,33 +656,37 @@ static void addMethodToProtocol(Protocol* protocol, NSString *selectorName, NSSt 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 = NSStringFromSelector(invocation.selector); + NSString *selectorName = isBlock ? @"" : NSStringFromSelector(invocation.selector); NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; - JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName); + JSValue *jsFunc = isBlock ? objc_getAssociatedObject(assignSlf, "_JSValue")[@"cb"] : getJSFunctionInObjectHierachy(slf, JPSelectorName); if (!jsFunc) { JPExecuteORIGForwardInvocation(slf, selector, invocation); return; } NSMutableArray *argList = [[NSMutableArray alloc] init]; - 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]]; + 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 = 2; i < numberOfArguments; i++) { + for (NSUInteger i = isBlock ? 1 : 2; i < numberOfArguments; i++) { const char *argumentType = [methodSignature getArgumentTypeAtIndex:i]; switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) { @@ -1415,6 +1419,14 @@ static id invokeVariableParameterMethod(NSMutableArray *origArgumentsList, NSMet 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])) { @@ -1427,35 +1439,102 @@ static id getArgument(id valObj){ static id genCallbackBlock(JSValue *jsVal) { - #define BLK_TRAITS_ARG(_idx, _paramName) \ - if (_idx < argTypes.count) { \ - NSString *argType = trim(argTypes[_idx]); \ - if (blockTypeIsScalarPointer(argType)) { \ - [list addObject:formatOCToJS([JPBoxing boxPointer:_paramName])]; \ - } else if (blockTypeIsObject(trim(argTypes[_idx]))) { \ - [list addObject:formatOCToJS((__bridge id)_paramName)]; \ - } else { \ - [list addObject:formatOCToJS([NSNumber numberWithLongLong:(long long)_paramName])]; \ - } \ - } - - NSArray *argTypes = [[jsVal[@"args"] toString] componentsSeparatedByString:@","]; - if (argTypes.count > [jsVal[@"argCount"] toInt32]) { - argTypes = [argTypes subarrayWithRange:NSMakeRange(1, argTypes.count - 1)]; - } - id cb = ^id(void *p0, void *p1, void *p2, void *p3, void *p4, void *p5) { - NSMutableArray *list = [[NSMutableArray alloc] init]; - BLK_TRAITS_ARG(0, p0) - BLK_TRAITS_ARG(1, p1) - BLK_TRAITS_ARG(2, p2) - BLK_TRAITS_ARG(3, p3) - BLK_TRAITS_ARG(4, p4) - BLK_TRAITS_ARG(5, p5) - JSValue *ret = [jsVal[@"cb"] callWithArguments:list]; - return formatJSToOC(ret); - }; + 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 cb; + return block; } #pragma mark - Struct