objc-js 0.0.13 → 0.0.14
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.
- package/README.md +56 -1
- package/binding.gyp +7 -2
- package/build/Release/nobjc_native.node +0 -0
- package/dist/index.d.ts +116 -1
- package/dist/index.js +117 -2
- package/dist/native.d.ts +2 -2
- package/dist/native.js +2 -2
- package/package.json +1 -1
- package/src/native/debug.h +19 -0
- package/src/native/ffi-utils.h +342 -0
- package/src/native/method-forwarding.h +5 -0
- package/src/native/method-forwarding.mm +104 -73
- package/src/native/nobjc.mm +3 -0
- package/src/native/protocol-impl.mm +6 -7
- package/src/native/protocol-storage.h +59 -0
- package/src/native/subclass-impl.h +20 -0
- package/src/native/subclass-impl.mm +1001 -0
- package/src/native/type-conversion.h +6 -8
package/README.md
CHANGED
|
@@ -21,9 +21,39 @@ const str = NSString.stringWithUTF8String$("Hello, World!");
|
|
|
21
21
|
console.log(str.toString());
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
### Subclassing Objective-C Classes
|
|
25
|
+
|
|
26
|
+
**objc-js** supports defining new Objective-C classes and subclassing existing ones from JavaScript. This allows you to override methods, implement custom behavior, and integrate deeply with macOS frameworks.
|
|
27
|
+
|
|
28
|
+
See [Subclassing Documentation](./docs/subclassing.md) for detailed usage and examples.
|
|
29
|
+
|
|
30
|
+
#### Quick Example
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { NobjcClass } from "objc-js";
|
|
34
|
+
|
|
35
|
+
const MyClass = NobjcClass.define({
|
|
36
|
+
name: "MyCustomClass",
|
|
37
|
+
superclass: "NSObject",
|
|
38
|
+
methods: {
|
|
39
|
+
description: {
|
|
40
|
+
types: "@@:", // returns NSString
|
|
41
|
+
implementation: (self) => {
|
|
42
|
+
const superDesc = NobjcClass.super(self, "description");
|
|
43
|
+
const prefix = NSString.stringWithUTF8String$("Custom: ");
|
|
44
|
+
return prefix.stringByAppendingString$(superDesc);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const instance = MyClass.alloc().init();
|
|
51
|
+
console.log(instance.description().toString()); // "Custom: <MyCustomClass: 0x...>"
|
|
52
|
+
```
|
|
53
|
+
|
|
24
54
|
### Protocol Implementation
|
|
25
55
|
|
|
26
|
-
**objc-js**
|
|
56
|
+
**objc-js** supports creating Objective-C protocol implementations from JavaScript. This allows you to create delegate objects that can be passed to Objective-C APIs.
|
|
27
57
|
|
|
28
58
|
#### Creating a Protocol Implementation
|
|
29
59
|
|
|
@@ -125,6 +155,31 @@ Wrapper for Objective-C objects. Methods can be called using the `$` notation.
|
|
|
125
155
|
const result = object.methodName$arg1$arg2$(arg1, arg2);
|
|
126
156
|
```
|
|
127
157
|
|
|
158
|
+
#### `NobjcClass`
|
|
159
|
+
|
|
160
|
+
Static class for defining new Objective-C classes and subclassing existing ones.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
NobjcClass.define(definition: ClassDefinition): NobjcObject
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Parameters:**
|
|
167
|
+
|
|
168
|
+
- `definition.name`: The name of the new Objective-C class (must be unique)
|
|
169
|
+
- `definition.superclass`: The name of the superclass (e.g., "NSObject", "NSView")
|
|
170
|
+
- `definition.protocols`: (Optional) Array of protocol names to conform to
|
|
171
|
+
- `definition.methods`: Object mapping selector names to method definitions
|
|
172
|
+
|
|
173
|
+
**Returns:** The new class object (can call `.alloc().init()` on it)
|
|
174
|
+
|
|
175
|
+
See [Subclassing Documentation](./docs/subclassing.md) for more details.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
NobjcClass.super(self: NobjcObject, selector: string, ...args: any[]): any
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Call the superclass implementation of a method. Supports methods with any number of arguments, including methods with out-parameters (like `NSError**`).
|
|
182
|
+
|
|
128
183
|
#### `NobjcProtocol`
|
|
129
184
|
|
|
130
185
|
Static class for creating protocol implementations.
|
package/binding.gyp
CHANGED
|
@@ -6,18 +6,23 @@
|
|
|
6
6
|
"src/native/nobjc.mm",
|
|
7
7
|
"src/native/ObjcObject.mm",
|
|
8
8
|
"src/native/protocol-impl.mm",
|
|
9
|
-
"src/native/method-forwarding.mm"
|
|
9
|
+
"src/native/method-forwarding.mm",
|
|
10
|
+
"src/native/subclass-impl.mm"
|
|
10
11
|
],
|
|
11
12
|
"defines": [
|
|
12
13
|
"NODE_ADDON_API_CPP_EXCEPTIONS",
|
|
13
14
|
"NAPI_VERSION=6"
|
|
14
15
|
],
|
|
15
16
|
"include_dirs": [
|
|
16
|
-
"<!@(node -p \"require('node-addon-api').include\")"
|
|
17
|
+
"<!@(node -p \"require('node-addon-api').include\")",
|
|
18
|
+
"<!@(pkg-config --cflags-only-I libffi | sed 's/-I//g')"
|
|
17
19
|
],
|
|
18
20
|
"dependencies": [
|
|
19
21
|
"<!(node -p \"require('node-addon-api').gyp\")"
|
|
20
22
|
],
|
|
23
|
+
"libraries": [
|
|
24
|
+
"-lffi"
|
|
25
|
+
],
|
|
21
26
|
"xcode_settings": {
|
|
22
27
|
"MACOSX_DEPLOYMENT_TARGET": "13.3",
|
|
23
28
|
"CLANG_CXX_LIBRARY": "libc++",
|
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -58,4 +58,119 @@ declare function getPointer(obj: NobjcObject): Buffer;
|
|
|
58
58
|
* (not deallocated) when you call this function.
|
|
59
59
|
*/
|
|
60
60
|
declare function fromPointer(pointer: Buffer | bigint): NobjcObject;
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Method definition for defining a class method.
|
|
63
|
+
*/
|
|
64
|
+
interface MethodDefinition {
|
|
65
|
+
/**
|
|
66
|
+
* Objective-C type encoding string.
|
|
67
|
+
* Common encodings:
|
|
68
|
+
* - @ = id (object)
|
|
69
|
+
* - : = SEL (selector)
|
|
70
|
+
* - v = void
|
|
71
|
+
* - B = BOOL
|
|
72
|
+
* - q = long long / NSInteger (64-bit)
|
|
73
|
+
* - Q = unsigned long long / NSUInteger (64-bit)
|
|
74
|
+
* - ^@ = id* (pointer to object, e.g., NSError**)
|
|
75
|
+
* - @? = block
|
|
76
|
+
*
|
|
77
|
+
* Example: "@@:@^@" means:
|
|
78
|
+
* - Return: @ (id)
|
|
79
|
+
* - self: @ (id)
|
|
80
|
+
* - _cmd: : (SEL)
|
|
81
|
+
* - arg1: @ (NSArray*)
|
|
82
|
+
* - arg2: ^@ (NSError**)
|
|
83
|
+
*/
|
|
84
|
+
types: string;
|
|
85
|
+
/**
|
|
86
|
+
* The JavaScript implementation.
|
|
87
|
+
* Receives (self, ...args) where self is the instance.
|
|
88
|
+
* For NSError** out-params, the arg is an object with { set(error), get() } methods.
|
|
89
|
+
*/
|
|
90
|
+
implementation: (self: NobjcObject, ...args: any[]) => any;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Class definition for creating a new Objective-C class.
|
|
94
|
+
*/
|
|
95
|
+
interface ClassDefinition {
|
|
96
|
+
/** Name of the new Objective-C class (must be unique in the runtime) */
|
|
97
|
+
name: string;
|
|
98
|
+
/** Superclass - either a class name string or a NobjcObject representing a Class */
|
|
99
|
+
superclass: string | NobjcObject;
|
|
100
|
+
/** Optional: protocols to conform to */
|
|
101
|
+
protocols?: string[];
|
|
102
|
+
/** Instance methods to implement/override */
|
|
103
|
+
methods?: Record<string, MethodDefinition>;
|
|
104
|
+
/** Optional: class methods */
|
|
105
|
+
classMethods?: Record<string, MethodDefinition>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* API for defining new Objective-C classes at runtime.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* // Define a subclass of NSObject
|
|
113
|
+
* const MyClass = NobjcClass.define({
|
|
114
|
+
* name: "MyClass",
|
|
115
|
+
* superclass: "NSObject",
|
|
116
|
+
* protocols: ["NSCopying"],
|
|
117
|
+
* methods: {
|
|
118
|
+
* "init": {
|
|
119
|
+
* types: "@@:",
|
|
120
|
+
* implementation: (self) => {
|
|
121
|
+
* return NobjcClass.super(self, "init");
|
|
122
|
+
* }
|
|
123
|
+
* },
|
|
124
|
+
* "myMethod:": {
|
|
125
|
+
* types: "@@:@",
|
|
126
|
+
* implementation: (self, arg) => {
|
|
127
|
+
* console.log("myMethod called with", arg);
|
|
128
|
+
* return arg;
|
|
129
|
+
* }
|
|
130
|
+
* }
|
|
131
|
+
* }
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* // Create an instance
|
|
135
|
+
* const instance = MyClass.alloc().init();
|
|
136
|
+
* instance.myMethod$(someArg);
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
declare class NobjcClass {
|
|
140
|
+
/**
|
|
141
|
+
* Define a new Objective-C class at runtime.
|
|
142
|
+
*
|
|
143
|
+
* @param definition The class definition
|
|
144
|
+
* @returns A NobjcObject representing the new Class (can be used to alloc/init instances)
|
|
145
|
+
*
|
|
146
|
+
* @warning For private methods (like _requestContextWithRequests:error:), you must provide
|
|
147
|
+
* the correct type encoding manually since it cannot be introspected from protocols.
|
|
148
|
+
*/
|
|
149
|
+
static define(definition: ClassDefinition): NobjcObject;
|
|
150
|
+
/**
|
|
151
|
+
* Call the superclass implementation of a method.
|
|
152
|
+
* Use this inside a method implementation to invoke super.
|
|
153
|
+
*
|
|
154
|
+
* @param self The instance (the first argument to your implementation)
|
|
155
|
+
* @param selector The selector string (e.g., "init" or "_requestContextWithRequests:error:")
|
|
156
|
+
* @param args Additional arguments to pass to super
|
|
157
|
+
* @returns The result of the super call
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* methods: {
|
|
162
|
+
* "init": {
|
|
163
|
+
* types: "@@:",
|
|
164
|
+
* implementation: (self) => {
|
|
165
|
+
* // Call [super init]
|
|
166
|
+
* const result = NobjcClass.super(self, "init");
|
|
167
|
+
* // Do additional setup...
|
|
168
|
+
* return result;
|
|
169
|
+
* }
|
|
170
|
+
* }
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
static super(self: NobjcObject, selector: string, ...args: any[]): any;
|
|
175
|
+
}
|
|
176
|
+
export { NobjcLibrary, NobjcObject, NobjcMethod, NobjcProtocol, NobjcClass, getPointer, fromPointer };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation } from "./native.js";
|
|
1
|
+
import { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation, DefineClass, CallSuper } from "./native.js";
|
|
2
2
|
const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom");
|
|
3
3
|
const NATIVE_OBJC_OBJECT = Symbol("nativeObjcObject");
|
|
4
4
|
class NobjcLibrary {
|
|
@@ -198,4 +198,119 @@ function fromPointer(pointer) {
|
|
|
198
198
|
}
|
|
199
199
|
return new NobjcObject(nativeObj);
|
|
200
200
|
}
|
|
201
|
-
|
|
201
|
+
/**
|
|
202
|
+
* API for defining new Objective-C classes at runtime.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* // Define a subclass of NSObject
|
|
207
|
+
* const MyClass = NobjcClass.define({
|
|
208
|
+
* name: "MyClass",
|
|
209
|
+
* superclass: "NSObject",
|
|
210
|
+
* protocols: ["NSCopying"],
|
|
211
|
+
* methods: {
|
|
212
|
+
* "init": {
|
|
213
|
+
* types: "@@:",
|
|
214
|
+
* implementation: (self) => {
|
|
215
|
+
* return NobjcClass.super(self, "init");
|
|
216
|
+
* }
|
|
217
|
+
* },
|
|
218
|
+
* "myMethod:": {
|
|
219
|
+
* types: "@@:@",
|
|
220
|
+
* implementation: (self, arg) => {
|
|
221
|
+
* console.log("myMethod called with", arg);
|
|
222
|
+
* return arg;
|
|
223
|
+
* }
|
|
224
|
+
* }
|
|
225
|
+
* }
|
|
226
|
+
* });
|
|
227
|
+
*
|
|
228
|
+
* // Create an instance
|
|
229
|
+
* const instance = MyClass.alloc().init();
|
|
230
|
+
* instance.myMethod$(someArg);
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
class NobjcClass {
|
|
234
|
+
/**
|
|
235
|
+
* Define a new Objective-C class at runtime.
|
|
236
|
+
*
|
|
237
|
+
* @param definition The class definition
|
|
238
|
+
* @returns A NobjcObject representing the new Class (can be used to alloc/init instances)
|
|
239
|
+
*
|
|
240
|
+
* @warning For private methods (like _requestContextWithRequests:error:), you must provide
|
|
241
|
+
* the correct type encoding manually since it cannot be introspected from protocols.
|
|
242
|
+
*/
|
|
243
|
+
static define(definition) {
|
|
244
|
+
// Convert method implementations to wrap args and unwrap returns
|
|
245
|
+
const nativeDefinition = {
|
|
246
|
+
name: definition.name,
|
|
247
|
+
superclass: typeof definition.superclass === "string" ? definition.superclass : unwrapArg(definition.superclass),
|
|
248
|
+
protocols: definition.protocols
|
|
249
|
+
};
|
|
250
|
+
if (definition.methods) {
|
|
251
|
+
nativeDefinition.methods = {};
|
|
252
|
+
for (const [selector, methodDef] of Object.entries(definition.methods)) {
|
|
253
|
+
const normalizedSelector = NobjcMethodNameToObjcSelector(selector);
|
|
254
|
+
nativeDefinition.methods[normalizedSelector] = {
|
|
255
|
+
types: methodDef.types,
|
|
256
|
+
implementation: (nativeSelf, ...nativeArgs) => {
|
|
257
|
+
// Wrap self
|
|
258
|
+
const wrappedSelf = wrapObjCObjectIfNeeded(nativeSelf);
|
|
259
|
+
// Wrap args, but preserve out-param objects as-is
|
|
260
|
+
const wrappedArgs = nativeArgs.map((arg) => {
|
|
261
|
+
// Check if it's an out-param object (has 'set' method)
|
|
262
|
+
if (arg && typeof arg === "object" && typeof arg.set === "function") {
|
|
263
|
+
// Keep out-param objects as-is, but wrap the error objects they handle
|
|
264
|
+
return {
|
|
265
|
+
set: (error) => arg.set(unwrapArg(error)),
|
|
266
|
+
get: () => wrapObjCObjectIfNeeded(arg.get())
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return wrapObjCObjectIfNeeded(arg);
|
|
270
|
+
});
|
|
271
|
+
// Call the user's implementation
|
|
272
|
+
const result = methodDef.implementation(wrappedSelf, ...wrappedArgs);
|
|
273
|
+
// Unwrap the return value
|
|
274
|
+
return unwrapArg(result);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Call native DefineClass
|
|
280
|
+
const nativeClass = DefineClass(nativeDefinition);
|
|
281
|
+
// Return wrapped Class object
|
|
282
|
+
return new NobjcObject(nativeClass);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Call the superclass implementation of a method.
|
|
286
|
+
* Use this inside a method implementation to invoke super.
|
|
287
|
+
*
|
|
288
|
+
* @param self The instance (the first argument to your implementation)
|
|
289
|
+
* @param selector The selector string (e.g., "init" or "_requestContextWithRequests:error:")
|
|
290
|
+
* @param args Additional arguments to pass to super
|
|
291
|
+
* @returns The result of the super call
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```typescript
|
|
295
|
+
* methods: {
|
|
296
|
+
* "init": {
|
|
297
|
+
* types: "@@:",
|
|
298
|
+
* implementation: (self) => {
|
|
299
|
+
* // Call [super init]
|
|
300
|
+
* const result = NobjcClass.super(self, "init");
|
|
301
|
+
* // Do additional setup...
|
|
302
|
+
* return result;
|
|
303
|
+
* }
|
|
304
|
+
* }
|
|
305
|
+
* }
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
static super(self, selector, ...args) {
|
|
309
|
+
const normalizedSelector = NobjcMethodNameToObjcSelector(selector);
|
|
310
|
+
const nativeSelf = unwrapArg(self);
|
|
311
|
+
const unwrappedArgs = args.map(unwrapArg);
|
|
312
|
+
const result = CallSuper(nativeSelf, normalizedSelector, ...unwrappedArgs);
|
|
313
|
+
return wrapObjCObjectIfNeeded(result);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
export { NobjcLibrary, NobjcObject, NobjcMethod, NobjcProtocol, NobjcClass, getPointer, fromPointer };
|
package/dist/native.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import * as _binding from "#nobjc_native";
|
|
2
|
-
declare const LoadLibrary: typeof _binding.LoadLibrary, GetClassObject: typeof _binding.GetClassObject, ObjcObject: typeof _binding.ObjcObject, GetPointer: typeof _binding.GetPointer, FromPointer: typeof _binding.FromPointer, CreateProtocolImplementation: typeof _binding.CreateProtocolImplementation;
|
|
3
|
-
export { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation };
|
|
2
|
+
declare const LoadLibrary: typeof _binding.LoadLibrary, GetClassObject: typeof _binding.GetClassObject, ObjcObject: typeof _binding.ObjcObject, GetPointer: typeof _binding.GetPointer, FromPointer: typeof _binding.FromPointer, CreateProtocolImplementation: typeof _binding.CreateProtocolImplementation, DefineClass: typeof _binding.DefineClass, CallSuper: typeof _binding.CallSuper;
|
|
3
|
+
export { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation, DefineClass, CallSuper };
|
|
4
4
|
export type { _binding as NobjcNative };
|
package/dist/native.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
const require = createRequire(import.meta.url);
|
|
3
3
|
const binding = require("#nobjc_native");
|
|
4
|
-
const { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation } = binding;
|
|
5
|
-
export { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation };
|
|
4
|
+
const { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation, DefineClass, CallSuper } = binding;
|
|
5
|
+
export { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation, DefineClass, CallSuper };
|
package/package.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"format": "prettier --write \"**/*.{ts,js,json,md}\"",
|
|
40
40
|
"preinstall-disabled": "npm run build-scripts && npm run make-clangd-config"
|
|
41
41
|
},
|
|
42
|
-
"version": "0.0.
|
|
42
|
+
"version": "0.0.14",
|
|
43
43
|
"description": "Objective-C bridge for Node.js",
|
|
44
44
|
"main": "dist/index.js",
|
|
45
45
|
"dependencies": {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#ifndef DEBUG_H
|
|
2
|
+
#define DEBUG_H
|
|
3
|
+
|
|
4
|
+
#define NOBJC_DEBUG 0
|
|
5
|
+
|
|
6
|
+
// Conditional logging macro - only logs when NOBJC_DEBUG is 1
|
|
7
|
+
#if NOBJC_DEBUG
|
|
8
|
+
#define NOBJC_LOG(fmt, ...) NSLog(@"" fmt, ##__VA_ARGS__)
|
|
9
|
+
#else
|
|
10
|
+
#define NOBJC_LOG(fmt, ...) do { } while(0)
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
// Always-on error logging
|
|
14
|
+
#define NOBJC_ERROR(fmt, ...) NSLog(@"ERROR: " fmt, ##__VA_ARGS__)
|
|
15
|
+
|
|
16
|
+
// Always-on warning logging
|
|
17
|
+
#define NOBJC_WARN(fmt, ...) NSLog(@"WARNING: " fmt, ##__VA_ARGS__)
|
|
18
|
+
|
|
19
|
+
#endif // DEBUG_H
|