Skip to content

Commit

Permalink
Add support for Cxx objects as arguments to native modules
Browse files Browse the repository at this point in the history
Reviewed By: fkgozali

Differential Revision: D5589269

fbshipit-source-id: 1bd7004adc397241cabfb1dc59ba1aebad943bf8
  • Loading branch information
fromcelticpark authored and facebook-github-bot committed Aug 14, 2017
1 parent 2a6965d commit 6783694
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 20 deletions.
10 changes: 10 additions & 0 deletions RNTester/RNTesterUnitTests/RCTMethodArgumentTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ - (void)testUntypedUnnamedArgs
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[2]).type, @"id");
}

- (void)testNamespacedCxxStruct
{
NSArray *arguments;
const char *methodSignature = "foo:(foo::type &)foo";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
XCTAssertEqual(arguments.count, (NSUInteger)1);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"foo::type");
}

- (void)testAttributes
{
NSArray *arguments;
Expand Down
20 changes: 20 additions & 0 deletions React/Base/RCTCxxConvert.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>

/**
* This class provides a collection of conversion functions for mapping
* JSON objects to cxx types. Extensible via categories.
* Convert methods are expected to return cxx objects wraped in RCTManagedPointer.
*/

@interface RCTCxxConvert : NSObject

@end
14 changes: 14 additions & 0 deletions React/Base/RCTCxxConvert.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTCxxConvert.h"

@implementation RCTCxxConvert

@end
34 changes: 34 additions & 0 deletions React/Base/RCTManagedPointer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#include <memory>

#import <Foundation/Foundation.h>

/**
* Type erased wrapper over any cxx value that can be passed as an argument
* to native method.
*/

@interface RCTManagedPointer: NSObject

@property (nonatomic, readonly) void *voidPointer;

- (instancetype)initWithPointer:(std::shared_ptr<void>)pointer;

@end

namespace RCT {
template <typename T, typename P>
RCTManagedPointer *managedPointer(P initializer)
{
auto ptr = std::shared_ptr<void>(new T((NSDictionary *)initializer));
return [[RCTManagedPointer alloc] initWithPointer:std::move(ptr)];
}
}
27 changes: 27 additions & 0 deletions React/Base/RCTManagedPointer.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTManagedPointer.h"

@implementation RCTManagedPointer {
std::shared_ptr<void> _pointer;
}

- (instancetype)initWithPointer:(std::shared_ptr<void>)pointer {
if (self = [super init]) {
_pointer = std::move(pointer);
}
return self;
}

- (void *)voidPointer {
return _pointer.get();
}

@end
32 changes: 27 additions & 5 deletions React/Base/RCTModuleMethod.m → React/Base/RCTModuleMethod.mm
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
#import "RCTBridge+Private.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTCxxConvert.h"
#import "RCTLog.h"
#import "RCTManagedPointer.h"
#import "RCTParserUtils.h"
#import "RCTProfile.h"
#import "RCTUtils.h"
Expand Down Expand Up @@ -60,12 +62,14 @@ static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index,

RCT_NOT_IMPLEMENTED(- (instancetype)init)

RCT_EXTERN_C_BEGIN

// returns YES if the selector ends in a colon (indicating that there is at
// least one argument, and maybe more selector parts) or NO if it doesn't.
static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector)
{
NSString *selectorPart;
if (RCTParseIdentifier(input, &selectorPart)) {
if (RCTParseSelectorIdentifier(input, &selectorPart)) {
[selector appendString:selectorPart];
}
RCTSkipWhitespace(input);
Expand Down Expand Up @@ -157,14 +161,16 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
}

// Argument name
RCTParseIdentifier(&input, NULL);
RCTParseArgumentIdentifier(&input, NULL);
RCTSkipWhitespace(&input);
}

*arguments = [args copy];
return NSSelectorFromString(selector);
}

RCT_EXTERN_C_END

- (instancetype)initWithExportedMethod:(const RCTMethodInfo *)exportedMethod
moduleClass:(Class)moduleClass
{
Expand Down Expand Up @@ -211,7 +217,7 @@ - (void)processMethodSignature

#define __PRIMITIVE_CASE(_type, _nullable) { \
isNullableType = _nullable; \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
_type (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend; \
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) { \
_type value = convert([RCTConvert class], selector, json); \
[invocation setArgument:&value atIndex:(index) + 2]; \
Expand Down Expand Up @@ -274,7 +280,7 @@ - (void)processMethodSignature

case _C_ID: {
isNullableType = YES;
id (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend;
id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCT_RETAINED_ARG_BLOCK(
id value = convert([RCTConvert class], selector, json);
);
Expand All @@ -300,7 +306,7 @@ - (void)processMethodSignature
}

default: {
static const char *blockType = @encode(typeof(^{}));
static const char *blockType = @encode(__typeof__(^{}));
if (!strcmp(objcType, blockType)) {
BLOCK_CASE((NSArray *args), {
[bridge enqueueCallback:json args:args];
Expand Down Expand Up @@ -334,6 +340,22 @@ - (void)processMethodSignature
NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
[bridge enqueueCallback:json args:@[errorJSON]];
});
} else if ([typeName hasPrefix:@"JS::"]) {
NSString *selectorNameForCxxType =
[[typeName stringByReplacingOccurrencesOfString:@"::" withString:@"_"]
stringByAppendingString:@":"];
selector = NSSelectorFromString(selectorNameForCxxType);

[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) {
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], selector, json);

void *pointer = box.voidPointer;
[invocation setArgument:&pointer atIndex:index + 2];
[retainedObjects addObject:box];

return YES;
}];
} else {
// Unknown argument type
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert to support this type.",
Expand Down
3 changes: 2 additions & 1 deletion React/Base/RCTParserUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
RCT_EXTERN BOOL RCTReadChar(const char **input, char c);
RCT_EXTERN BOOL RCTReadString(const char **input, const char *string);
RCT_EXTERN void RCTSkipWhitespace(const char **input);
RCT_EXTERN BOOL RCTParseIdentifier(const char **input, NSString **string);
RCT_EXTERN BOOL RCTParseSelectorIdentifier(const char **input, NSString **string);
RCT_EXTERN BOOL RCTParseArgumentIdentifier(const char **input, NSString **string);

/**
* Parse an Objective-C type into a form that can be used by RCTConvert.
Expand Down
29 changes: 27 additions & 2 deletions React/Base/RCTParserUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,32 @@ static BOOL RCTIsIdentifierTail(const char c)
return isalnum(c) || c == '_';
}

BOOL RCTParseIdentifier(const char **input, NSString **string)
BOOL RCTParseArgumentIdentifier(const char **input, NSString **string)
{
const char *start = *input;

do {
if (!RCTIsIdentifierHead(**input)) {
return NO;
}
(*input)++;

while (RCTIsIdentifierTail(**input)) {
(*input)++;
}

// allow namespace resolution operator
} while (RCTReadString(input, "::"));

if (string) {
*string = [[NSString alloc] initWithBytes:start
length:(NSInteger)(*input - start)
encoding:NSASCIIStringEncoding];
}
return YES;
}

BOOL RCTParseSelectorIdentifier(const char **input, NSString **string)
{
const char *start = *input;
if (!RCTIsIdentifierHead(**input)) {
Expand Down Expand Up @@ -83,7 +108,7 @@ static BOOL RCTIsCollectionType(NSString *type)
NSString *RCTParseType(const char **input)
{
NSString *type;
RCTParseIdentifier(input, &type);
RCTParseArgumentIdentifier(input, &type);
RCTSkipWhitespace(input);
if (RCTReadChar(input, '<')) {
RCTSkipWhitespace(input);
Expand Down
Loading

0 comments on commit 6783694

Please sign in to comment.