Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
#import <objc/runtime.h>
#import <atomic>
#import <iostream>
#import <memory>
#import <mutex>

#import <sstream>
#import <string>
#import <type_traits>
#import <vector>

using namespace facebook;
Expand All @@ -49,65 +51,135 @@ static int32_t getUniqueId()
/**
* All static helper functions are ObjC++ specific.
*/
static jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value)
static jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, __unsafe_unretained NSNumber *value)
{
return jsi::Value((bool)[value boolValue]);
}

static jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value)
static jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, __unsafe_unretained NSNumber *value)
{
return jsi::Value([value doubleValue]);
}

static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
// Creates a JS string or property name from the string's own ASCII or UTF-16 characters.
template <typename T>
static T convertNSStringToJSI(jsi::Runtime &runtime, __unsafe_unretained NSString *value)
{
auto fromAscii = [&](const char *ascii, size_t length) {
if constexpr (std::is_same_v<T, jsi::PropNameID>) {
return jsi::PropNameID::forAscii(runtime, ascii, length);
} else {
return jsi::String::createFromAscii(runtime, ascii, length);
}
};
auto fromUtf16 = [&](const UniChar *characters, size_t length) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
auto utf16 = reinterpret_cast<const char16_t *>(characters);
if constexpr (std::is_same_v<T, jsi::PropNameID>) {
return jsi::PropNameID::forUtf16(runtime, utf16, length);
} else {
return jsi::String::createFromUtf16(runtime, utf16, length);
}
};

if (value == nil) {
return jsi::String::createFromAscii(runtime, "", 0);
return fromAscii("", 0);
}

// Hand the JS runtime the string's own ASCII or UTF-16 characters when
// CoreFoundation can expose them, and copy them once otherwise.
auto string = (__bridge CFStringRef)value;
CFIndex length = CFStringGetLength(string);
if (const char *ascii = CFStringGetCStringPtr(string, kCFStringEncodingASCII)) {
return jsi::String::createFromAscii(runtime, ascii, static_cast<size_t>(length));
return fromAscii(ascii, static_cast<size_t>(length));
}
if (const UniChar *characters = CFStringGetCharactersPtr(string)) {
return jsi::String::createFromUtf16(
runtime,
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<const char16_t *>(characters),
static_cast<size_t>(length));
return fromUtf16(characters, static_cast<size_t>(length));
}
// Short strings, such as most keys, are often stored in the pointer itself, so copy them to the stack.
constexpr CFIndex kStackLength = 64;
if (length <= kStackLength) {
UInt8 ascii[kStackLength];
CFIndex asciiLength = 0;
if (CFStringGetBytes(
string, CFRangeMake(0, length), kCFStringEncodingASCII, 0, false, ascii, length, &asciiLength) == length) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
return fromAscii(reinterpret_cast<const char *>(ascii), static_cast<size_t>(asciiLength));
}
UniChar characters[kStackLength];
CFStringGetCharacters(string, CFRangeMake(0, length), characters);
return fromUtf16(characters, static_cast<size_t>(length));
}
std::unique_ptr<UniChar[]> characters(new UniChar[length]);
CFStringGetCharacters(string, CFRangeMake(0, length), characters.get());
return fromUtf16(characters.get(), static_cast<size_t>(length));
}

static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
{
return convertNSStringToJSI<jsi::String>(runtime, value);
}

// Doesn't retain `value`: the caller holds it, and containers hold what they contain while it is converted.
static jsi::Value convertUnretainedObjCObjectToJSIValue(jsi::Runtime &runtime, __unsafe_unretained id value);

// Reads a container's objects at once and holds them while they are converted, because JS code
// that runs while properties are set could change the container.
template <typename ReadObjects>
static std::vector<id> getRetainedObjects(NSUInteger count, ReadObjects readObjects)
{
constexpr NSUInteger kStackCount = 32;
__unsafe_unretained id stackObjects[kStackCount];
std::vector<__unsafe_unretained id> heapObjects;
__unsafe_unretained id *objects = stackObjects;
if (count > kStackCount) {
heapObjects.resize(count);
objects = heapObjects.data();
}
std::u16string utf16(length, u'\0');
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
CFStringGetCharacters(string, CFRangeMake(0, length), reinterpret_cast<UniChar *>(utf16.data()));
return jsi::String::createFromUtf16(runtime, utf16);
readObjects(objects);
return std::vector<id>(objects, objects + count);
}

static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, __unsafe_unretained NSDictionary *value)
{
NSUInteger count = value.count;
std::vector<id> keysAndObjects = getRetainedObjects(count * 2, [&](__unsafe_unretained id *objects) {
[value getObjects:objects + count andKeys:objects count:count];
});
jsi::Object result = jsi::Object(runtime);
for (NSString *k in value) {
result.setProperty(runtime, convertNSStringToJSIString(runtime, k), convertObjCObjectToJSIValue(runtime, value[k]));
for (NSUInteger i = 0; i < count; i++) {
result.setProperty(
runtime,
convertNSStringToJSI<jsi::PropNameID>(runtime, keysAndObjects[i]),
convertUnretainedObjCObjectToJSIValue(runtime, keysAndObjects[count + i]));
}
return result;
}

static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value)
static std::vector<id> getRetainedObjects(__unsafe_unretained NSArray *value)
{
NSUInteger count = value.count;
return getRetainedObjects(
count, [&](__unsafe_unretained id *objects) { [value getObjects:objects range:NSMakeRange(0, count)]; });
}

static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, __unsafe_unretained NSArray *value)
{
jsi::Array result = jsi::Array(runtime, value.count);
for (size_t i = 0; i < value.count; i++) {
result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i]));
std::vector<id> objects = getRetainedObjects(value);
jsi::Array result = jsi::Array(runtime, objects.size());
for (size_t i = 0; i < objects.size(); i++) {
result.setValueAtIndex(runtime, i, convertUnretainedObjCObjectToJSIValue(runtime, objects[i]));
}
return result;
}

static std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value)
static std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, __unsafe_unretained NSArray *value)
{
std::vector<id> objects = getRetainedObjects(value);
std::vector<jsi::Value> result;
for (size_t i = 0; i < value.count; i++) {
result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i]));
result.reserve(objects.size());
for (id object : objects) {
result.emplace_back(convertUnretainedObjCObjectToJSIValue(runtime, object));
}
return result;
}
Expand Down Expand Up @@ -152,9 +224,14 @@ size_t size() const override
}

jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
{
return convertUnretainedObjCObjectToJSIValue(runtime, value);
}

static jsi::Value convertUnretainedObjCObjectToJSIValue(jsi::Runtime &runtime, __unsafe_unretained id value)
{
if ([value isKindOfClass:[NSString class]]) {
return convertNSStringToJSIString(runtime, (NSString *)value);
return convertNSStringToJSI<jsi::String>(runtime, value);
} else if ([value isKindOfClass:[NSNumber class]]) {
if ([value isKindOfClass:[@YES class]]) {
return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value);
Expand Down
Loading