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 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** now supports creating Objective-C protocol implementations from JavaScript. This allows you to create delegate objects that can be passed to Objective-C APIs.
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
- export { NobjcLibrary, NobjcObject, NobjcMethod, NobjcProtocol, getPointer, fromPointer };
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
- export { NobjcLibrary, NobjcObject, NobjcMethod, NobjcProtocol, getPointer, fromPointer };
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.13",
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