objc-js 0.0.15 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,33 @@
1
1
  #ifndef TYPE_CONVERSION_H
2
2
  #define TYPE_CONVERSION_H
3
3
 
4
+ /**
5
+ * @file type-conversion.h
6
+ * @brief Type conversion utilities for nobjc.
7
+ *
8
+ * This header provides utilities for converting between JavaScript and
9
+ * Objective-C types. It includes:
10
+ *
11
+ * - Type Encoding Utilities:
12
+ * - SimplifiedTypeEncoding: Class to strip type qualifiers from ObjC encodings
13
+ * - SimplifyTypeEncoding(): Legacy function for the same purpose
14
+ *
15
+ * - ObjC to JS Conversion:
16
+ * - ObjCToJS(): Convert ObjC value at pointer to JS value
17
+ * - ExtractInvocationArgumentToJS(): Extract NSInvocation arg to JS
18
+ * - GetInvocationReturnAsJS(): Get NSInvocation return value as JS
19
+ *
20
+ * - JS to ObjC Conversion:
21
+ * - SetInvocationReturnFromJS(): Set NSInvocation return from JS value
22
+ *
23
+ * The conversion functions use a visitor pattern (via type-dispatch.h) to
24
+ * handle different type codes with minimal code duplication.
25
+ *
26
+ * @see type-dispatch.h for the underlying dispatch mechanism
27
+ */
28
+
4
29
  #include "ObjcObject.h"
30
+ #include "type-dispatch.h"
5
31
  #include <Foundation/Foundation.h>
6
32
  #include <napi.h>
7
33
  #include <objc/runtime.h>
@@ -10,229 +36,165 @@
10
36
  // MARK: - Type Encoding Utilities
11
37
 
12
38
  // Helper class to manage the lifetime of simplified type encodings
39
+ // Optimized to use pointer offset instead of string::erase()
13
40
  class SimplifiedTypeEncoding {
14
41
  private:
15
- std::string simplified;
42
+ const char* original;
43
+ size_t offset; // Offset past any leading qualifiers
44
+
45
+ // Check if a character is a type qualifier
46
+ static bool IsQualifier(char c) {
47
+ // r=const, n=in, N=inout, o=out, O=bycopy, R=byref, V=oneway
48
+ return c == 'r' || c == 'n' || c == 'N' || c == 'o' ||
49
+ c == 'O' || c == 'R' || c == 'V';
50
+ }
16
51
 
17
52
  public:
18
- SimplifiedTypeEncoding(const char *typeEncoding) : simplified(typeEncoding) {
19
- // Remove any leading qualifiers (r=const, n=in, N=inout, o=out, O=bycopy,
20
- // R=byref, V=oneway)
21
- while (!simplified.empty() &&
22
- (simplified[0] == 'r' || simplified[0] == 'n' ||
23
- simplified[0] == 'N' || simplified[0] == 'o' ||
24
- simplified[0] == 'O' || simplified[0] == 'R' ||
25
- simplified[0] == 'V')) {
26
- simplified.erase(0, 1);
53
+ SimplifiedTypeEncoding(const char *typeEncoding)
54
+ : original(typeEncoding), offset(0) {
55
+ // Skip leading qualifiers using pointer arithmetic (O(k) where k = qualifier count)
56
+ if (original) {
57
+ while (original[offset] != '\0' && IsQualifier(original[offset])) {
58
+ ++offset;
59
+ }
27
60
  }
28
61
  }
29
62
 
30
- const char *c_str() const { return simplified.c_str(); }
31
- char operator[](size_t index) const { return simplified[index]; }
32
- operator const char *() const { return simplified.c_str(); }
63
+ const char *c_str() const { return original ? original + offset : ""; }
64
+ char operator[](size_t index) const {
65
+ return original ? original[offset + index] : '\0';
66
+ }
67
+ operator const char *() const { return c_str(); }
68
+
69
+ // Check if empty (after stripping qualifiers)
70
+ bool empty() const { return !original || original[offset] == '\0'; }
33
71
  };
34
72
 
35
73
  // Legacy function for compatibility - returns pointer to internal string
36
- // WARNING: The returned pointer is only valid as long as the typeEncoding
37
- // parameter is valid
74
+ // Optimized to use pointer arithmetic instead of string mutations
38
75
  inline const char *SimplifyTypeEncoding(const char *typeEncoding) {
39
- // For simple cases where there are no qualifiers, return the original pointer
40
- if (typeEncoding && typeEncoding[0] != 'r' && typeEncoding[0] != 'n' &&
41
- typeEncoding[0] != 'N' && typeEncoding[0] != 'o' &&
42
- typeEncoding[0] != 'O' && typeEncoding[0] != 'R' &&
43
- typeEncoding[0] != 'V') {
44
- return typeEncoding;
45
- }
46
-
47
- // For complex cases, we need to skip qualifiers
48
- // This is a temporary fix - callers should use SimplifiedTypeEncoding class
49
- static thread_local std::string buffer;
50
- buffer = typeEncoding;
51
- while (!buffer.empty() &&
52
- (buffer[0] == 'r' || buffer[0] == 'n' || buffer[0] == 'N' ||
53
- buffer[0] == 'o' || buffer[0] == 'O' || buffer[0] == 'R' ||
54
- buffer[0] == 'V')) {
55
- buffer.erase(0, 1);
56
- }
57
- return buffer.c_str();
76
+ if (!typeEncoding) return "";
77
+
78
+ // Skip leading qualifiers using pointer arithmetic
79
+ const char* ptr = typeEncoding;
80
+ while (*ptr == 'r' || *ptr == 'n' || *ptr == 'N' || *ptr == 'o' ||
81
+ *ptr == 'O' || *ptr == 'R' || *ptr == 'V') {
82
+ ++ptr;
83
+ }
84
+ return ptr;
58
85
  }
59
86
 
60
87
  // MARK: - ObjC to JS Conversion
61
88
 
62
- // Convert an Objective-C value (from a pointer) to a JavaScript value
63
- inline Napi::Value ObjCToJS(Napi::Env env, void *valuePtr, char typeCode) {
64
- switch (typeCode) {
65
- case 'c': {
66
- char value = *(char *)valuePtr;
67
- return Napi::Number::New(env, value);
68
- }
69
- case 'i': {
70
- int value = *(int *)valuePtr;
71
- return Napi::Number::New(env, value);
72
- }
73
- case 's': {
74
- short value = *(short *)valuePtr;
75
- return Napi::Number::New(env, value);
76
- }
77
- case 'l': {
78
- long value = *(long *)valuePtr;
79
- return Napi::Number::New(env, value);
80
- }
81
- case 'q': {
82
- long long value = *(long long *)valuePtr;
83
- return Napi::Number::New(env, value);
84
- }
85
- case 'C': {
86
- unsigned char value = *(unsigned char *)valuePtr;
87
- return Napi::Number::New(env, value);
88
- }
89
- case 'I': {
90
- unsigned int value = *(unsigned int *)valuePtr;
91
- return Napi::Number::New(env, value);
92
- }
93
- case 'S': {
94
- unsigned short value = *(unsigned short *)valuePtr;
95
- return Napi::Number::New(env, value);
96
- }
97
- case 'L': {
98
- unsigned long value = *(unsigned long *)valuePtr;
99
- return Napi::Number::New(env, value);
100
- }
101
- case 'Q': {
102
- unsigned long long value = *(unsigned long long *)valuePtr;
103
- return Napi::Number::New(env, value);
104
- }
105
- case 'f': {
106
- float value = *(float *)valuePtr;
107
- return Napi::Number::New(env, value);
108
- }
109
- case 'd': {
110
- double value = *(double *)valuePtr;
111
- return Napi::Number::New(env, value);
89
+ // Visitor for converting ObjC values to JS
90
+ struct ObjCToJSVisitor {
91
+ Napi::Env env;
92
+ void* valuePtr;
93
+
94
+ // Numeric types -> Number (or Boolean for bool)
95
+ template <typename T>
96
+ auto operator()(std::type_identity<T>) const
97
+ -> std::enable_if_t<is_numeric_v<T> && !std::is_same_v<T, bool>, Napi::Value> {
98
+ T value = *static_cast<T*>(valuePtr);
99
+ return Napi::Number::New(env, static_cast<double>(value));
112
100
  }
113
- case 'B': {
114
- bool value = *(bool *)valuePtr;
101
+
102
+ // Bool -> Boolean
103
+ Napi::Value operator()(std::type_identity<bool>) const {
104
+ bool value = *static_cast<bool*>(valuePtr);
115
105
  return Napi::Boolean::New(env, value);
116
106
  }
117
- case '*': {
118
- char *value = *(char **)valuePtr;
107
+
108
+ // C string -> String or Null
109
+ Napi::Value operator()(std::type_identity<ObjCCStringTag>) const {
110
+ char* value = *static_cast<char**>(valuePtr);
119
111
  if (value == nullptr) {
120
112
  return env.Null();
121
113
  }
122
114
  return Napi::String::New(env, value);
123
115
  }
124
- case '@': {
125
- id value = *(__strong id *)valuePtr;
116
+
117
+ // id -> ObjcObject or Null
118
+ Napi::Value operator()(std::type_identity<ObjCIdTag>) const {
119
+ id value = *static_cast<__strong id*>(valuePtr);
126
120
  if (value == nil) {
127
121
  return env.Null();
128
122
  }
129
123
  return ObjcObject::NewInstance(env, value);
130
124
  }
131
- case '#': {
132
- Class value = *(Class *)valuePtr;
125
+
126
+ // Class -> ObjcObject or Null
127
+ Napi::Value operator()(std::type_identity<ObjCClassTag>) const {
128
+ Class value = *static_cast<Class*>(valuePtr);
133
129
  if (value == nil) {
134
130
  return env.Null();
135
131
  }
136
132
  return ObjcObject::NewInstance(env, value);
137
133
  }
138
- case ':': {
139
- SEL value = *(SEL *)valuePtr;
134
+
135
+ // SEL -> String or Null
136
+ Napi::Value operator()(std::type_identity<ObjCSELTag>) const {
137
+ SEL value = *static_cast<SEL*>(valuePtr);
140
138
  if (value == nullptr) {
141
139
  return env.Null();
142
140
  }
143
- NSString *selectorString = NSStringFromSelector(value);
141
+ NSString* selectorString = NSStringFromSelector(value);
144
142
  if (selectorString == nil) {
145
143
  return env.Null();
146
144
  }
147
145
  return Napi::String::New(env, [selectorString UTF8String]);
148
146
  }
149
- case 'v':
147
+
148
+ // Pointer -> Undefined (not fully supported)
149
+ Napi::Value operator()(std::type_identity<ObjCPointerTag>) const {
150
150
  return env.Undefined();
151
- default:
151
+ }
152
+
153
+ // Void -> Undefined
154
+ Napi::Value operator()(std::type_identity<ObjCVoidTag>) const {
152
155
  return env.Undefined();
153
156
  }
157
+ };
158
+
159
+ // Convert an Objective-C value (from a pointer) to a JavaScript value
160
+ inline Napi::Value ObjCToJS(Napi::Env env, void *valuePtr, char typeCode) {
161
+ return DispatchByTypeCode(typeCode, ObjCToJSVisitor{env, valuePtr});
154
162
  }
155
163
 
156
- // Extract an argument from NSInvocation and convert to JS value
157
- inline Napi::Value ExtractInvocationArgumentToJS(Napi::Env env,
158
- NSInvocation *invocation,
159
- NSUInteger index,
160
- char typeCode) {
161
- switch (typeCode) {
162
- case 'c': {
163
- char value;
164
- [invocation getArgument:&value atIndex:index];
165
- return Napi::Number::New(env, value);
166
- }
167
- case 'i': {
168
- int value;
169
- [invocation getArgument:&value atIndex:index];
170
- return Napi::Number::New(env, value);
171
- }
172
- case 's': {
173
- short value;
174
- [invocation getArgument:&value atIndex:index];
175
- return Napi::Number::New(env, value);
176
- }
177
- case 'l': {
178
- long value;
179
- [invocation getArgument:&value atIndex:index];
180
- return Napi::Number::New(env, value);
181
- }
182
- case 'q': {
183
- long long value;
184
- [invocation getArgument:&value atIndex:index];
185
- return Napi::Number::New(env, value);
186
- }
187
- case 'C': {
188
- unsigned char value;
189
- [invocation getArgument:&value atIndex:index];
190
- return Napi::Number::New(env, value);
191
- }
192
- case 'I': {
193
- unsigned int value;
194
- [invocation getArgument:&value atIndex:index];
195
- return Napi::Number::New(env, value);
196
- }
197
- case 'S': {
198
- unsigned short value;
199
- [invocation getArgument:&value atIndex:index];
200
- return Napi::Number::New(env, value);
201
- }
202
- case 'L': {
203
- unsigned long value;
204
- [invocation getArgument:&value atIndex:index];
205
- return Napi::Number::New(env, value);
206
- }
207
- case 'Q': {
208
- unsigned long long value;
209
- [invocation getArgument:&value atIndex:index];
210
- return Napi::Number::New(env, value);
211
- }
212
- case 'f': {
213
- float value;
214
- [invocation getArgument:&value atIndex:index];
215
- return Napi::Number::New(env, value);
216
- }
217
- case 'd': {
218
- double value;
164
+ // Visitor for extracting NSInvocation arguments to JS
165
+ struct ExtractInvocationArgVisitor {
166
+ Napi::Env env;
167
+ NSInvocation* invocation;
168
+ NSUInteger index;
169
+
170
+ // Numeric types -> Number (or Boolean for bool)
171
+ template <typename T>
172
+ auto operator()(std::type_identity<T>) const
173
+ -> std::enable_if_t<is_numeric_v<T> && !std::is_same_v<T, bool>, Napi::Value> {
174
+ T value;
219
175
  [invocation getArgument:&value atIndex:index];
220
- return Napi::Number::New(env, value);
176
+ return Napi::Number::New(env, static_cast<double>(value));
221
177
  }
222
- case 'B': {
178
+
179
+ // Bool -> Boolean
180
+ Napi::Value operator()(std::type_identity<bool>) const {
223
181
  bool value;
224
182
  [invocation getArgument:&value atIndex:index];
225
183
  return Napi::Boolean::New(env, value);
226
184
  }
227
- case '*': {
228
- char *value;
185
+
186
+ // C string -> String or Null
187
+ Napi::Value operator()(std::type_identity<ObjCCStringTag>) const {
188
+ char* value;
229
189
  [invocation getArgument:&value atIndex:index];
230
190
  if (value == nullptr) {
231
191
  return env.Null();
232
192
  }
233
193
  return Napi::String::New(env, value);
234
194
  }
235
- case '@': {
195
+
196
+ // id -> ObjcObject or Null
197
+ Napi::Value operator()(std::type_identity<ObjCIdTag>) const {
236
198
  __unsafe_unretained id value;
237
199
  [invocation getArgument:&value atIndex:index];
238
200
  if (value == nil) {
@@ -240,7 +202,9 @@ inline Napi::Value ExtractInvocationArgumentToJS(Napi::Env env,
240
202
  }
241
203
  return ObjcObject::NewInstance(env, value);
242
204
  }
243
- case '#': {
205
+
206
+ // Class -> ObjcObject or Null
207
+ Napi::Value operator()(std::type_identity<ObjCClassTag>) const {
244
208
  Class value;
245
209
  [invocation getArgument:&value atIndex:index];
246
210
  if (value == nil) {
@@ -248,29 +212,43 @@ inline Napi::Value ExtractInvocationArgumentToJS(Napi::Env env,
248
212
  }
249
213
  return ObjcObject::NewInstance(env, value);
250
214
  }
251
- case ':': {
215
+
216
+ // SEL -> String or Null
217
+ Napi::Value operator()(std::type_identity<ObjCSELTag>) const {
252
218
  SEL value;
253
219
  [invocation getArgument:&value atIndex:index];
254
220
  if (value == nullptr) {
255
221
  return env.Null();
256
222
  }
257
- NSString *selString = NSStringFromSelector(value);
223
+ NSString* selString = NSStringFromSelector(value);
258
224
  if (selString == nil) {
259
225
  return env.Null();
260
226
  }
261
227
  return Napi::String::New(env, [selString UTF8String]);
262
228
  }
263
- case '^': {
264
- void *value;
229
+
230
+ // Pointer -> Undefined (not fully supported)
231
+ Napi::Value operator()(std::type_identity<ObjCPointerTag>) const {
232
+ void* value;
265
233
  [invocation getArgument:&value atIndex:index];
266
234
  if (value == nullptr) {
267
235
  return env.Null();
268
236
  }
269
237
  return env.Undefined();
270
238
  }
271
- default:
239
+
240
+ // Void -> Undefined
241
+ Napi::Value operator()(std::type_identity<ObjCVoidTag>) const {
272
242
  return env.Undefined();
273
243
  }
244
+ };
245
+
246
+ // Extract an argument from NSInvocation and convert to JS value
247
+ inline Napi::Value ExtractInvocationArgumentToJS(Napi::Env env,
248
+ NSInvocation *invocation,
249
+ NSUInteger index,
250
+ char typeCode) {
251
+ return DispatchByTypeCode(typeCode, ExtractInvocationArgVisitor{env, invocation, index});
274
252
  }
275
253
 
276
254
  // MARK: - JS to ObjC Return Value Conversion
@@ -491,90 +469,49 @@ inline void SetInvocationReturnFromJS(NSInvocation *invocation,
491
469
 
492
470
  // MARK: - Return Value Extraction from NSInvocation
493
471
 
494
- // Get return value from NSInvocation and convert to JS
495
- inline Napi::Value GetInvocationReturnAsJS(Napi::Env env,
496
- NSInvocation *invocation,
497
- NSMethodSignature *methodSignature) {
498
- SimplifiedTypeEncoding returnType([methodSignature methodReturnType]);
472
+ // Visitor for getting return values from NSInvocation
473
+ struct GetInvocationReturnVisitor {
474
+ Napi::Env env;
475
+ NSInvocation* invocation;
499
476
 
500
- switch (returnType[0]) {
501
- case 'c': {
502
- char result;
503
- [invocation getReturnValue:&result];
504
- return Napi::Number::New(env, result);
505
- }
506
- case 'i': {
507
- int result;
508
- [invocation getReturnValue:&result];
509
- return Napi::Number::New(env, result);
510
- }
511
- case 's': {
512
- short result;
513
- [invocation getReturnValue:&result];
514
- return Napi::Number::New(env, result);
515
- }
516
- case 'l': {
517
- long result;
518
- [invocation getReturnValue:&result];
519
- return Napi::Number::New(env, result);
520
- }
521
- case 'q': {
522
- long long result;
523
- [invocation getReturnValue:&result];
524
- return Napi::Number::New(env, result);
525
- }
526
- case 'C': {
527
- unsigned char result;
528
- [invocation getReturnValue:&result];
529
- return Napi::Number::New(env, result);
530
- }
531
- case 'I': {
532
- unsigned int result;
533
- [invocation getReturnValue:&result];
534
- return Napi::Number::New(env, result);
535
- }
536
- case 'S': {
537
- unsigned short result;
477
+ // Numeric types -> Number (or Boolean for bool)
478
+ template <typename T>
479
+ auto operator()(std::type_identity<T>) const
480
+ -> std::enable_if_t<is_numeric_v<T> && !std::is_same_v<T, bool>, Napi::Value> {
481
+ T result;
538
482
  [invocation getReturnValue:&result];
539
- return Napi::Number::New(env, result);
483
+ return Napi::Number::New(env, static_cast<double>(result));
540
484
  }
541
- case 'L': {
542
- unsigned long result;
543
- [invocation getReturnValue:&result];
544
- return Napi::Number::New(env, result);
545
- }
546
- case 'Q': {
547
- unsigned long long result;
548
- [invocation getReturnValue:&result];
549
- return Napi::Number::New(env, result);
550
- }
551
- case 'f': {
552
- float result;
553
- [invocation getReturnValue:&result];
554
- return Napi::Number::New(env, result);
555
- }
556
- case 'd': {
557
- double result;
558
- [invocation getReturnValue:&result];
559
- return Napi::Number::New(env, result);
560
- }
561
- case 'B': {
485
+
486
+ // Bool -> Boolean
487
+ Napi::Value operator()(std::type_identity<bool>) const {
562
488
  bool result;
563
489
  [invocation getReturnValue:&result];
564
490
  return Napi::Boolean::New(env, result);
565
491
  }
566
- case 'v':
567
- return env.Undefined();
568
- case '*': {
569
- char *result = nullptr;
492
+
493
+ // C string -> String or Null
494
+ Napi::Value operator()(std::type_identity<ObjCCStringTag>) const {
495
+ char* result = nullptr;
570
496
  [invocation getReturnValue:&result];
571
497
  if (result == nullptr) {
572
498
  return env.Null();
573
499
  }
574
500
  return Napi::String::New(env, result);
575
501
  }
576
- case '@':
577
- case '#': {
502
+
503
+ // id -> ObjcObject or Null
504
+ Napi::Value operator()(std::type_identity<ObjCIdTag>) const {
505
+ id result = nil;
506
+ [invocation getReturnValue:&result];
507
+ if (result == nil) {
508
+ return env.Null();
509
+ }
510
+ return ObjcObject::NewInstance(env, result);
511
+ }
512
+
513
+ // Class -> ObjcObject or Null (same as id)
514
+ Napi::Value operator()(std::type_identity<ObjCClassTag>) const {
578
515
  id result = nil;
579
516
  [invocation getReturnValue:&result];
580
517
  if (result == nil) {
@@ -582,23 +519,40 @@ inline Napi::Value GetInvocationReturnAsJS(Napi::Env env,
582
519
  }
583
520
  return ObjcObject::NewInstance(env, result);
584
521
  }
585
- case ':': {
522
+
523
+ // SEL -> String or Null
524
+ Napi::Value operator()(std::type_identity<ObjCSELTag>) const {
586
525
  SEL result = nullptr;
587
526
  [invocation getReturnValue:&result];
588
527
  if (result == nullptr) {
589
528
  return env.Null();
590
529
  }
591
- NSString *selectorString = NSStringFromSelector(result);
530
+ NSString* selectorString = NSStringFromSelector(result);
592
531
  if (selectorString == nil) {
593
532
  return env.Null();
594
533
  }
595
534
  return Napi::String::New(env, [selectorString UTF8String]);
596
535
  }
597
- default:
598
- Napi::TypeError::New(env, "Unsupported return type (post-invoke)")
536
+
537
+ // Pointer -> Error (unsupported)
538
+ Napi::Value operator()(std::type_identity<ObjCPointerTag>) const {
539
+ Napi::TypeError::New(env, "Unsupported return type (pointer)")
599
540
  .ThrowAsJavaScriptException();
600
541
  return env.Null();
601
542
  }
543
+
544
+ // Void -> Undefined
545
+ Napi::Value operator()(std::type_identity<ObjCVoidTag>) const {
546
+ return env.Undefined();
547
+ }
548
+ };
549
+
550
+ // Get return value from NSInvocation and convert to JS
551
+ inline Napi::Value GetInvocationReturnAsJS(Napi::Env env,
552
+ NSInvocation *invocation,
553
+ NSMethodSignature *methodSignature) {
554
+ SimplifiedTypeEncoding returnType([methodSignature methodReturnType]);
555
+ return DispatchByTypeCode(returnType[0], GetInvocationReturnVisitor{env, invocation});
602
556
  }
603
557
 
604
558
  #endif // TYPE_CONVERSION_H