From aa837b807affa0b2406257d35a3b7e574acd17e7 Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Thu, 3 Mar 2022 15:36:16 +0800 Subject: [PATCH] iOS: use category method to translate JSValue to object --- doric-iOS/Example/Podfile | 4 +- doric-iOS/Pod/Classes/Engine/DoricJSEngine.m | 11 +- doric-iOS/Pod/Classes/Engine/JSValue+Doric.h | 2 + doric-iOS/Pod/Classes/Engine/JSValue+Doric.m | 31 --- doric-iOS/Pod/Classes/Engine/JSValue+Doric.mm | 189 ++++++++++++++++++ 5 files changed, 201 insertions(+), 36 deletions(-) delete mode 100644 doric-iOS/Pod/Classes/Engine/JSValue+Doric.m create mode 100644 doric-iOS/Pod/Classes/Engine/JSValue+Doric.mm diff --git a/doric-iOS/Example/Podfile b/doric-iOS/Example/Podfile index afce9339..2ec5e827 100644 --- a/doric-iOS/Example/Podfile +++ b/doric-iOS/Example/Podfile @@ -8,9 +8,9 @@ target 'Example' do pod 'DoricCore', :path => '../../' pod 'DoricDevkit', :path => '../../' - pod 'YYWebImage' + #pod 'YYWebImage' - pod 'YYImage/WebP' + #pod 'YYImage/WebP' pod 'SDWebImage' diff --git a/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m b/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m index d581071d..741c1f42 100644 --- a/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m +++ b/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m @@ -29,6 +29,7 @@ #import "DoricContext.h" #import "DoricContextManager.h" #import "DoricPerformanceProfile.h" +#import "JSValue+Doric.h" @interface DoricDefaultMonitor : NSObject @end @@ -210,8 +211,8 @@ - (void)initJSExecutor { } }]; - [self.jsExecutor injectGlobalJSObject:INJECT_BRIDGE obj:^(NSString *contextId, NSString *module, NSString *method, NSString *callbackId, id argument) { - return [self.bridgeExtension callNativeWithContextId:contextId module:module method:method callbackId:callbackId argument:argument]; + [self.jsExecutor injectGlobalJSObject:INJECT_BRIDGE obj:^(NSString *contextId, NSString *module, NSString *method, NSString *callbackId, JSValue *argument) { + return [self.bridgeExtension callNativeWithContextId:contextId module:module method:method callbackId:callbackId argument:[self jsValueToObject:argument]]; }]; } @@ -264,7 +265,7 @@ - (JSValue *)invokeDoricMethod:(NSString *)method arguments:(va_list)args { - (JSValue *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args { JSValue *ret = [self.jsExecutor invokeObject:GLOBAL_DORIC method:method args:args]; if (![method isEqualToString:@"pureCallEntityMethod"]) { - [self.jsExecutor invokeObject:GLOBAL_DORIC method:DORIC_HOOK_NATIVE_CALL args:nil]; + [self.jsExecutor invokeObject:GLOBAL_DORIC method:DORIC_HOOK_NATIVE_CALL args:@[]]; } return ret; } @@ -311,4 +312,8 @@ - (void)callbackTimer:(NSTimer *)timer { } }]; } + +- (id)jsValueToObject:(JSValue *)jsValue { + return [jsValue toObjectWithArrayBuffer]; +} @end diff --git a/doric-iOS/Pod/Classes/Engine/JSValue+Doric.h b/doric-iOS/Pod/Classes/Engine/JSValue+Doric.h index b1a8c3a6..e59f952a 100644 --- a/doric-iOS/Pod/Classes/Engine/JSValue+Doric.h +++ b/doric-iOS/Pod/Classes/Engine/JSValue+Doric.h @@ -9,4 +9,6 @@ - (BOOL)isArrayBuffer; - (NSData *)toArrayBuffer; + +- (id)toObjectWithArrayBuffer; @end \ No newline at end of file diff --git a/doric-iOS/Pod/Classes/Engine/JSValue+Doric.m b/doric-iOS/Pod/Classes/Engine/JSValue+Doric.m deleted file mode 100644 index 72379e3e..00000000 --- a/doric-iOS/Pod/Classes/Engine/JSValue+Doric.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// Created by pengfei.zhou on 2021/11/19. -// - -#import "JSValue+Doric.h" - -@implementation JSValue (Doric) -- (BOOL)isArrayBuffer { - JSContextRef ctx = self.context.JSGlobalContextRef; - JSValueRef jsValueRef = self.JSValueRef; - if (self.isObject) { - JSTypedArrayType type = JSValueGetTypedArrayType(ctx, jsValueRef, NULL); - return type == kJSTypedArrayTypeArrayBuffer; - } - return NO; -} - -- (NSData *)toArrayBuffer { - if (!self.isArrayBuffer) { - return nil; - } - JSContextRef ctx = self.context.JSGlobalContextRef; - JSValueRef jsValueRef = self.JSValueRef; - JSObjectRef ref = JSValueToObject(ctx, jsValueRef, NULL); - size_t size = JSObjectGetArrayBufferByteLength(ctx, ref, NULL); - void *ptr = JSObjectGetArrayBufferBytesPtr(ctx, ref, NULL); - - return [[NSData alloc] initWithBytesNoCopy:ptr length:size freeWhenDone:NO]; -} - -@end diff --git a/doric-iOS/Pod/Classes/Engine/JSValue+Doric.mm b/doric-iOS/Pod/Classes/Engine/JSValue+Doric.mm new file mode 100644 index 00000000..1769a53a --- /dev/null +++ b/doric-iOS/Pod/Classes/Engine/JSValue+Doric.mm @@ -0,0 +1,189 @@ +// +// Created by pengfei.zhou on 2021/11/19. +// + +#import "JSValue+Doric.h" +#import +#import + +enum ConversionType { + ContainerNone, + ContainerArray, + ContainerDictionary +}; + +class JSContainerConvertor { +public: + struct Task { + JSValueRef js; + id objc; + ConversionType type; + }; + + JSContainerConvertor(JSGlobalContextRef context) + : m_context(context) { + } + + id convert(JSValueRef property); + + void add(Task); + + Task take(); + + bool isWorkListEmpty() const { + return !m_worklist.size(); + } + +private: + JSGlobalContextRef m_context; + std::unordered_map m_objectMap; + std::vector m_worklist; +}; + +static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task) { + assert(task.type != ContainerNone); + JSContainerConvertor convertor(context); + convertor.add(task); + assert(!convertor.isWorkListEmpty()); + + do { + JSContainerConvertor::Task current = convertor.take(); + assert(JSValueIsObject(context, current.js)); + JSObjectRef js = JSValueToObject(context, current.js, 0); + + if (current.type == ContainerArray) { + assert([current.objc isKindOfClass:[NSMutableArray class]]); + NSMutableArray *array = (NSMutableArray *) current.objc; + + auto lengthString = JSStringCreateWithUTF8CString("length"); + unsigned length = static_cast(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0)); + JSStringRelease(lengthString); + for (unsigned i = 0; i < length; ++i) { + id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0)); + [array addObject:objc ? objc : [NSNull null]]; + } + } else { + assert([current.objc isKindOfClass:[NSMutableDictionary class]]); + NSMutableDictionary *dictionary = (NSMutableDictionary *) current.objc; + JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js); + size_t length = JSPropertyNameArrayGetCount(propertyNameArray); + for (size_t i = 0; i < length; ++i) { + JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i); + if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0))) { + CFStringRef cfString = JSStringCopyCFString(kCFAllocatorDefault, propertyName); + NSString *key = (__bridge NSString *) cfString; + dictionary[key] = objc; + CFRelease(cfString); + } + } + JSPropertyNameArrayRelease(propertyNameArray); + } + + } while (!convertor.isWorkListEmpty()); + + return task.objc; +} + +static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value) { + if (!JSValueIsObject(context, value)) { + id primitive; + if (JSValueIsBoolean(context, value)) + primitive = JSValueToBoolean(context, value) ? @YES : @NO; + else if (JSValueIsNumber(context, value)) { + // Normalize the number, so it will unique correctly in the hash map - + // it's nicer not to leak this internal implementation detail! + value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0)); + primitive = @(JSValueToNumber(context, value, 0)); + } else if (JSValueIsString(context, value)) { + // Would be nice to unique strings, too. + JSStringRef jsString = JSValueToStringCopy(context, value, 0); + primitive = CFBridgingRelease(JSStringCopyCFString(kCFAllocatorDefault, jsString)); + JSStringRelease(jsString); + } else if (JSValueIsNull(context, value)) { + primitive = [NSNull null]; + } else { + primitive = nil; + } + return {value, primitive, ContainerNone}; + } + + JSObjectRef object = JSValueToObject(context, value, 0); + JSTypedArrayType type = JSValueGetTypedArrayType(context, value, NULL); + if (type == kJSTypedArrayTypeArrayBuffer) { + size_t size = JSObjectGetArrayBufferByteLength(context, object, NULL); + void *ptr = JSObjectGetArrayBufferBytesPtr(context, object, NULL); + id primitive = [[NSData alloc] initWithBytesNoCopy:ptr length:size freeWhenDone:NO]; + return {value, primitive, ContainerNone}; + } + if (JSValueIsArray(context, value)) + return {object, [NSMutableArray array], ContainerArray}; + + return {object, [NSMutableDictionary dictionary], ContainerDictionary}; +} + +@implementation JSValue (Doric) + + +inline id JSContainerConvertor::convert(JSValueRef value) { + auto iter = m_objectMap.find(value); + if (iter != m_objectMap.end()) { + return iter->second; + } + + Task result = valueToObjectWithoutCopy(m_context, value); + if (result.js) + add(result); + return result.objc; +} + +void JSContainerConvertor::add(Task task) { + if (task.type != ContainerNone) + m_worklist.push_back(task); + else + m_objectMap.insert({task.js, task.objc}); + +} + +JSContainerConvertor::Task JSContainerConvertor::take() { + assert(!isWorkListEmpty()); + Task last = m_worklist.back(); + m_worklist.pop_back(); + return last; +} + + +id valueToObject(JSContext *context, JSValueRef value) { + JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value); + if (result.type == ContainerNone) + return result.objc; + return containerValueToObject([context JSGlobalContextRef], result); +} + +- (BOOL)isArrayBuffer { + JSContextRef ctx = self.context.JSGlobalContextRef; + JSValueRef jsValueRef = self.JSValueRef; + if (self.isObject) { + JSTypedArrayType type = JSValueGetTypedArrayType(ctx, jsValueRef, NULL); + return type == kJSTypedArrayTypeArrayBuffer; + } + return NO; +} + +- (NSData *)toArrayBuffer { + if (!self.isArrayBuffer) { + return nil; + } + JSContextRef ctx = self.context.JSGlobalContextRef; + JSValueRef jsValueRef = self.JSValueRef; + JSObjectRef ref = JSValueToObject(ctx, jsValueRef, NULL); + size_t size = JSObjectGetArrayBufferByteLength(ctx, ref, NULL); + void *ptr = JSObjectGetArrayBufferBytesPtr(ctx, ref, NULL); + + return [[NSData alloc] initWithBytesNoCopy:ptr length:size freeWhenDone:NO]; +} + +- (id)toObjectWithArrayBuffer { + return valueToObject(self.context, self.JSValueRef); +} + +@end