iOS: use category method to translate JSValue to object

This commit is contained in:
pengfei.zhou 2022-03-03 15:36:16 +08:00 committed by osborn
parent 85a03bdd93
commit aa837b807a
5 changed files with 201 additions and 36 deletions

View File

@ -8,9 +8,9 @@ target 'Example' do
pod 'DoricCore', :path => '../../' pod 'DoricCore', :path => '../../'
pod 'DoricDevkit', :path => '../../' pod 'DoricDevkit', :path => '../../'
pod 'YYWebImage' #pod 'YYWebImage'
pod 'YYImage/WebP' #pod 'YYImage/WebP'
pod 'SDWebImage' pod 'SDWebImage'

View File

@ -29,6 +29,7 @@
#import "DoricContext.h" #import "DoricContext.h"
#import "DoricContextManager.h" #import "DoricContextManager.h"
#import "DoricPerformanceProfile.h" #import "DoricPerformanceProfile.h"
#import "JSValue+Doric.h"
@interface DoricDefaultMonitor : NSObject <DoricMonitorProtocol> @interface DoricDefaultMonitor : NSObject <DoricMonitorProtocol>
@end @end
@ -210,8 +211,8 @@ - (void)initJSExecutor {
} }
}]; }];
[self.jsExecutor injectGlobalJSObject:INJECT_BRIDGE obj:^(NSString *contextId, NSString *module, NSString *method, NSString *callbackId, id 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: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 *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args {
JSValue *ret = [self.jsExecutor invokeObject:GLOBAL_DORIC method:method args:args]; JSValue *ret = [self.jsExecutor invokeObject:GLOBAL_DORIC method:method args:args];
if (![method isEqualToString:@"pureCallEntityMethod"]) { 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; return ret;
} }
@ -311,4 +312,8 @@ - (void)callbackTimer:(NSTimer *)timer {
} }
}]; }];
} }
- (id)jsValueToObject:(JSValue *)jsValue {
return [jsValue toObjectWithArrayBuffer];
}
@end @end

View File

@ -9,4 +9,6 @@
- (BOOL)isArrayBuffer; - (BOOL)isArrayBuffer;
- (NSData *)toArrayBuffer; - (NSData *)toArrayBuffer;
- (id)toObjectWithArrayBuffer;
@end @end

View File

@ -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

View File

@ -0,0 +1,189 @@
//
// Created by pengfei.zhou on 2021/11/19.
//
#import "JSValue+Doric.h"
#import <vector>
#import <unordered_map>
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<JSValueRef, __unsafe_unretained id> m_objectMap;
std::vector<Task> 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<unsigned int>(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