Skip to content

Native Objective‑C/Swift Wrapper for the ExecuTorch Value #8364

Open
@shoumikhin

Description

@shoumikhin

🚀 The feature, motivation and pitch

ExecuTorchValue is a lightweight Objective‑C wrapper for the C++ EValue type. It provides a unified variant type that can hold a tensor, a scalar (boolean, integer, or double), a string, or lists of tensors/scalars. The wrapper uses standard Objective‑C types (NSNumber, NSString, NSArray) to represent these values, and it is designed to be easily bridged into Swift via NS_SWIFT_NAME annotations, so that users can create and manipulate values in an idiomatic way without having to deal with the underlying C++ complexity.

By using ExecuTorchValue, developers no longer need to write custom bridging code for converting between C++ EValues and Objective‑C types. This simplifies integration, improves type safety, and minimizes performance overhead. In Swift the value type appears simply as Value with natural initializers, for example Value(3.14), Value("Hello"), Value([1, 2, 3]) or Value(Tensor(...)).

Alternatives

See the parent issue #8360 for motivation and alternatives overview.

Additional context

  • The wrapper supports the most common types needed for model inputs and outputs: tensor, boolean, integer, double, string, and lists of tensors or scalars.
  • Advanced features (such as support for optional tensor lists or separate lists for booleans, integers, or doubles) are supported via the fact NSNumber is already a variant type for different scalar values, and NSArray as the list type may contain NSNull values denoting optional or missing values.
  • The design focuses on minimal overhead and seamless interoperation with ExecuTorch’s Module wrapper.

RFC (Optional)

Internal Representation

The ExecuTorchValue wrapper holds a simple tag and an associated Objective‑C object. The tag (of type ExecuTorchValueTag) indicates the type of value stored (e.g. tensor, string, double, integer, boolean, tensor list, scalar list). The internal value is stored as an id and is used to represent scalars via NSNumber, strings via NSString, and lists via NSArray.

ExecuTorchValueTag _tag;
id _value;

Key Types:

The tag type directly matches the one in executorch/runtime/core/tag.h:

typedef NS_ENUM(uint32_t, ExecuTorchValueTag) {
  ExecuTorchValueTagNone,
  ExecuTorchValueTagTensor,
  ExecuTorchValueTagString,
  ExecuTorchValueTagDouble,
  ExecuTorchValueTagInteger,
  ExecuTorchValueTagBoolean,
  ExecuTorchValueTagBooleanList,
  ExecuTorchValueTagDoubleList,
  ExecuTorchValueTagIntegerList,
  ExecuTorchValueTagTensorList,
  ExecuTorchValueTagScalarList,
  ExecuTorchValueTagOptionalTensorList,
} NS_SWIFT_NAME(ValueTag);

The actual data types are aliased as the following:

typedef NSNumber *ExecuTorchScalarValue NS_SWIFT_NAME(ScalarValue);
typedef BOOL ExecuTorchBooleanValue NS_SWIFT_NAME(BoolValue);
typedef NSInteger ExecuTorchIntegerValue NS_SWIFT_NAME(IntegerValue);
typedef double ExecuTorchDoubleValue NS_SWIFT_NAME(DoubleValue);
typedef NSString *ExecuTorchStringValue NS_SWIFT_NAME(StringValue);
typedef NSArray<ExecuTorchTensor *> *ExecuTorchTensorListValue NS_SWIFT_NAME(TensorListValue);
typedef NSArray<ExecuTorchScalarValue> *ExecuTorchScalarListValue NS_SWIFT_NAME(ScalarListValue);

Those may need an extra NS_SWIFT_BRIDGED_TYPEDEF attribute to bridge well into Swift.

Key Methods:

  • Initialization:

These class methods create a new ExecuTorchValue instance with the appropriate tag and underlying value.

Objective-C

+ (instancetype)valueWithTensor:(ExecuTorchTensor *)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithBoolean:(ExecuTorchBooleanValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithInteger:(ExecuTorchIntegerValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithDouble:(ExecuTorchDoubleValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithString:(ExecuTorchStringValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithTensorList:(ExecuTorchTensorListValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithScalarList:(ExecuTorchScalarListValue)value NS_SWIFT_NAME(init(_:));

Swift:

let tensorValue = Value(someTensor)
let boolValue = Value(true)
let intValue = Value(42)
let doubleValue = Value(3.14)
let stringValue = Value("Hello")
let tensorList = Value([tensor1, tensor2])
let scalarList = Value([1, 2, 3])
  • Properties:

The wrapper exposes properties to retrieve the stored value in its native type if it matches the expected type:

Objective-C

@property(nonatomic, readonly) ExecuTorchValueTag tag;
@property(nullable, nonatomic, readonly) ExecuTorchTensor *tensorValue NS_SWIFT_NAME(tensor);
@property(nullable, nonatomic, readonly) ExecuTorchScalarValue scalarValue NS_SWIFT_NAME(scalar);
@property(nonatomic, readonly) ExecuTorchBooleanValue boolValue NS_SWIFT_NAME(boolean);
@property(nonatomic, readonly) ExecuTorchIntegerValue intValue NS_SWIFT_NAME(integer);
@property(nonatomic, readonly) ExecuTorchDoubleValue doubleValue NS_SWIFT_NAME(double);
@property(nullable, nonatomic, readonly) ExecuTorchStringValue stringValue NS_SWIFT_NAME(string);
@property(nullable, nonatomic, readonly) ExecuTorchTensorListValue tensorListValue NS_SWIFT_NAME(tensorList);
@property(nullable, nonatomic, readonly) ExecuTorchScalarListValue scalarListValue NS_SWIFT_NAME(scalarList);

They will return nil if the Value is of a different type, and for the numeric types that cannot be nil we can leverage NSParameterAssert instead to check for correctness.

Swift

let value = Value(42)
print(value.tag)      // e.g., prints .integer
print(value.integer)  // prints 42
print(value.scalar)   // prints 42 as NSNumber
print(value.tensor)   // prints nil, since value does not contain a tensor

let textValue = Value("Hello")
print(textValue.tag)   // prints .string
print(textValue.string) // prints "Hello"
  • Type Checking

There should be a bunch of readonly properties to check if the Value is of a certain type:

@property(nonatomic, readonly) BOOL isNone;
@property(nonatomic, readonly) BOOL isTensor;
@property(nonatomic, readonly) BOOL isScalar;
@property(nonatomic, readonly) BOOL isBoolean;
@property(nonatomic, readonly) BOOL isInteger;
@property(nonatomic, readonly) BOOL isDouble;
@property(nonatomic, readonly) BOOL isString;
@property(nonatomic, readonly) BOOL isTensorList;
@property(nonatomic, readonly) BOOL isScalarList;

That will be used as following:

Objective-C

Value *value = [Value valueWithInteger:42];
if (value.isInteger) {
  NSLog(@"Integer: %@", value.intValue);
} else {
  NSLog(@"Not an integer");
}

Value *tensorValue = [Value valueWithTensor:someTensor];
if (tensorValue.isTensor) {
  NSLog(@"Tensor: %@", tensorValue.tensorValue);
} else {
  NSLog(@"Not a tensor");
}

Swift

let value1 = Value(42)
if value1.isInteger {
  print("Integer: \(value1.integer)")
} else {
  print("Not an integer")
}

let value2 = Value(someTensor)
if value2.isTensor {
  print("Tensor: \(value2.tensor)")
} else {
  print("Not a tensor")
}

let value3 = Value("Hello")
if value3.isString {
  print("String: \(value3.string)")
} else {
  print("Not a string")
}
  • Equality:

It also provides convenience methods to check the type of the stored value and an equality method:

Objective-C

- (BOOL)isEqualToValue:(nullable ExecuTorchValue *)other;

Swift:

let isTensor = value.tag == .tensor
let isEqual = value == anotherValue

A sample implementation may look like the following:

- (BOOL)isEqual:(nullable id)other {
  if (self == other) {
    return YES;
  }
  if (![other isKindOfClass:[ExecuTorchValue class]]) {
    return NO;
  }
  return [self isEqualToValue:(ExecuTorchValue *)other];
}

- (BOOL)isEqualToValue:(nullable ExecuTorchValue *)other {
  if (!other) {
    return NO;
  }
  return _tag == other->_tag && [_value isEqual:other->_value];
}
  • Description

It comes handy to implement some -description method for better logging and debugging:

- (NSString *)description {
  return [NSString stringWithFormat:@"Value {\ntag=%i,\nvalue=%@}", _tag, _value];
}
  • Error Handling & Reporting:

The wrapper’s accessor properties are intentionally nullable. When an accessor is called for a type that does not match the stored value (for example, calling tensor on a scalar value), it returns nil per Objective‑C conventions. This design safely reports missing values without triggering runtime errors and provides a natural experience in Swift.

Metadata

Metadata

Assignees

Labels

module: iosIssues related to iOS code, build, and executiontriagedThis issue has been looked at a team member, and triaged and prioritized into an appropriate module

Type

No type

Projects

Status

Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions