objcjs-types 0.2.1 → 0.4.0
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/bin/objcjs-types.ts +31 -0
- package/dist/AVFoundation/functions.d.ts +21 -0
- package/dist/AVFoundation/functions.js +32 -0
- package/dist/Accessibility/functions.d.ts +16 -0
- package/dist/Accessibility/functions.js +35 -0
- package/dist/AddressBook/functions.d.ts +98 -0
- package/dist/AddressBook/functions.js +290 -0
- package/dist/AppKit/functions.d.ts +112 -0
- package/dist/AppKit/functions.js +272 -0
- package/dist/AudioToolbox/functions.d.ts +377 -0
- package/dist/AudioToolbox/functions.js +1124 -0
- package/dist/AuthenticationServices/functions.d.ts +2 -0
- package/dist/AuthenticationServices/functions.js +5 -0
- package/dist/BrowserEngineCore/functions.d.ts +3 -0
- package/dist/BrowserEngineCore/functions.js +11 -0
- package/dist/CoreAudio/functions.d.ts +60 -0
- package/dist/CoreAudio/functions.js +173 -0
- package/dist/CoreMIDI/functions.d.ts +96 -0
- package/dist/CoreMIDI/functions.js +287 -0
- package/dist/CoreML/functions.d.ts +2 -0
- package/dist/CoreML/functions.js +5 -0
- package/dist/CoreMediaIO/functions.d.ts +38 -0
- package/dist/CoreMediaIO/functions.js +107 -0
- package/dist/CoreText/functions.d.ts +209 -0
- package/dist/CoreText/functions.js +611 -0
- package/dist/CoreWLAN/functions.d.ts +23 -0
- package/dist/CoreWLAN/functions.js +56 -0
- package/dist/DeviceDiscoveryExtension/functions.d.ts +11 -0
- package/dist/DeviceDiscoveryExtension/functions.js +17 -0
- package/dist/DiscRecording/functions.d.ts +97 -0
- package/dist/DiscRecording/functions.js +290 -0
- package/dist/DiscRecordingUI/functions.d.ts +13 -0
- package/dist/DiscRecordingUI/functions.js +38 -0
- package/dist/ExceptionHandling/functions.d.ts +1 -0
- package/dist/ExceptionHandling/functions.js +5 -0
- package/dist/FSKit/functions.d.ts +4 -0
- package/dist/FSKit/functions.js +11 -0
- package/dist/Foundation/functions.d.ts +145 -0
- package/dist/Foundation/functions.js +386 -0
- package/dist/GLKit/functions.d.ts +51 -0
- package/dist/GLKit/functions.js +146 -0
- package/dist/GameController/functions.d.ts +18 -0
- package/dist/GameController/functions.js +44 -0
- package/dist/HealthKit/functions.d.ts +19 -0
- package/dist/HealthKit/functions.js +35 -0
- package/dist/IOSurface/functions.d.ts +53 -0
- package/dist/IOSurface/functions.js +155 -0
- package/dist/IOUSBHost/functions.d.ts +44 -0
- package/dist/IOUSBHost/functions.js +131 -0
- package/dist/InstantMessage/functions.d.ts +1 -0
- package/dist/InstantMessage/functions.js +5 -0
- package/dist/JavaRuntimeSupport/functions.d.ts +40 -0
- package/dist/JavaRuntimeSupport/functions.js +113 -0
- package/dist/JavaScriptCore/functions.d.ts +120 -0
- package/dist/JavaScriptCore/functions.js +359 -0
- package/dist/MLCompute/functions.d.ts +27 -0
- package/dist/MLCompute/functions.js +41 -0
- package/dist/MapKit/functions.d.ts +23 -0
- package/dist/MapKit/functions.js +56 -0
- package/dist/Matter/functions.d.ts +17 -0
- package/dist/Matter/functions.js +26 -0
- package/dist/MediaAccessibility/functions.d.ts +28 -0
- package/dist/MediaAccessibility/functions.js +83 -0
- package/dist/MediaPlayer/functions.d.ts +3 -0
- package/dist/MediaPlayer/functions.js +11 -0
- package/dist/Metal/functions.d.ts +14 -0
- package/dist/Metal/functions.js +26 -0
- package/dist/MetalKit/functions.d.ts +11 -0
- package/dist/MetalKit/functions.js +20 -0
- package/dist/MetalPerformanceShaders/functions.d.ts +7 -0
- package/dist/MetalPerformanceShaders/functions.js +14 -0
- package/dist/NearbyInteraction/functions.d.ts +3 -0
- package/dist/NearbyInteraction/functions.js +5 -0
- package/dist/ParavirtualizedGraphics/functions.d.ts +7 -0
- package/dist/ParavirtualizedGraphics/functions.js +14 -0
- package/dist/QuartzCore/functions.d.ts +19 -0
- package/dist/QuartzCore/functions.js +50 -0
- package/dist/SceneKit/functions.d.ts +17 -0
- package/dist/SceneKit/functions.js +38 -0
- package/dist/SensorKit/functions.d.ts +4 -0
- package/dist/SensorKit/functions.js +14 -0
- package/dist/ServiceManagement/functions.d.ts +7 -0
- package/dist/ServiceManagement/functions.js +20 -0
- package/dist/StoreKit/functions.d.ts +1 -0
- package/dist/StoreKit/functions.js +5 -0
- package/dist/VideoToolbox/functions.d.ts +81 -0
- package/dist/VideoToolbox/functions.js +236 -0
- package/dist/Vision/functions.d.ts +16 -0
- package/dist/Vision/functions.js +38 -0
- package/generator/ast-parser.ts +1368 -0
- package/generator/clang.ts +167 -0
- package/generator/custom.ts +936 -0
- package/generator/discover.ts +111 -0
- package/generator/emitter.ts +2020 -0
- package/generator/frameworks.ts +135 -0
- package/generator/index.ts +1334 -0
- package/generator/parse-worker.ts +263 -0
- package/generator/resolve-strings.ts +121 -0
- package/generator/struct-fields.ts +46 -0
- package/generator/templates/bind.ts +100 -0
- package/generator/templates/helpers.ts +70 -0
- package/generator/templates/nsdata.ts +97 -0
- package/generator/templates/osversion.ts +91 -0
- package/generator/type-mapper.ts +615 -0
- package/generator/worker-pool.ts +309 -0
- package/package.json +17 -4
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker pool for parallel header parsing.
|
|
3
|
+
* Manages a fixed pool of worker threads and distributes parse tasks
|
|
4
|
+
* with automatic load balancing via an internal queue.
|
|
5
|
+
*
|
|
6
|
+
* Each worker runs clang + AST parsing independently. The pool routes
|
|
7
|
+
* tasks to idle workers and queues excess tasks until a worker is free.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
ObjCClass,
|
|
12
|
+
ObjCProtocol,
|
|
13
|
+
ObjCIntegerEnum,
|
|
14
|
+
ObjCStringEnum,
|
|
15
|
+
ObjCStruct,
|
|
16
|
+
ObjCStructAlias,
|
|
17
|
+
ObjCFunction
|
|
18
|
+
} from "./ast-parser.ts";
|
|
19
|
+
|
|
20
|
+
/** Result from a unified parse-all task (classes + protocols + enums + structs from one clang invocation) */
|
|
21
|
+
export interface UnifiedParseResult {
|
|
22
|
+
/** Parsed classes from the header (class name → ObjCClass) */
|
|
23
|
+
classes: Map<string, ObjCClass>;
|
|
24
|
+
/** Parsed protocols from the header (protocol name → ObjCProtocol) */
|
|
25
|
+
protocols: Map<string, ObjCProtocol>;
|
|
26
|
+
/** Parsed integer enums from the header (enum name → ObjCIntegerEnum) */
|
|
27
|
+
integerEnums: Map<string, ObjCIntegerEnum>;
|
|
28
|
+
/** Parsed string enums from the header (enum name → ObjCStringEnum) */
|
|
29
|
+
stringEnums: Map<string, ObjCStringEnum>;
|
|
30
|
+
/** Parsed struct definitions from the header (struct name → ObjCStruct) */
|
|
31
|
+
structs: Map<string, ObjCStruct>;
|
|
32
|
+
/** Struct typedef aliases (e.g., NSPoint → CGPoint) */
|
|
33
|
+
structAliases: ObjCStructAlias[];
|
|
34
|
+
/** General typedef resolution table (typedef name → underlying qualType) */
|
|
35
|
+
typedefs: Map<string, string>;
|
|
36
|
+
/** Parsed C function declarations (function name → ObjCFunction) */
|
|
37
|
+
functions: Map<string, ObjCFunction>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ClassParseResult {
|
|
41
|
+
/** Parsed classes from the header (class name → ObjCClass) */
|
|
42
|
+
classes: Map<string, ObjCClass>;
|
|
43
|
+
/** Original target class names that were requested */
|
|
44
|
+
targets: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProtocolParseResult {
|
|
48
|
+
/** Parsed protocols from the header (protocol name → ObjCProtocol) */
|
|
49
|
+
protocols: Map<string, ObjCProtocol>;
|
|
50
|
+
/** Original target protocol names that were requested */
|
|
51
|
+
targets: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface EnumParseResult {
|
|
55
|
+
/** Parsed integer enums from the header (enum name → ObjCIntegerEnum) */
|
|
56
|
+
integerEnums: Map<string, ObjCIntegerEnum>;
|
|
57
|
+
/** Parsed string enums from the header (enum name → ObjCStringEnum) */
|
|
58
|
+
stringEnums: Map<string, ObjCStringEnum>;
|
|
59
|
+
/** Original target integer enum names that were requested */
|
|
60
|
+
integerTargets: string[];
|
|
61
|
+
/** Original target string enum names that were requested */
|
|
62
|
+
stringTargets: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface PendingTask {
|
|
66
|
+
resolve: (value: any) => void;
|
|
67
|
+
reject: (error: Error) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface QueuedTask {
|
|
71
|
+
message: any;
|
|
72
|
+
resolve: (value: any) => void;
|
|
73
|
+
reject: (error: Error) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Pool of worker threads for running clang + AST parsing in parallel.
|
|
78
|
+
* Tasks are automatically load-balanced: each new task goes to an idle
|
|
79
|
+
* worker, or queues until one becomes available.
|
|
80
|
+
*/
|
|
81
|
+
export class WorkerPool {
|
|
82
|
+
private workers: Worker[];
|
|
83
|
+
private idle: Worker[];
|
|
84
|
+
private queue: QueuedTask[];
|
|
85
|
+
private pending: Map<Worker, PendingTask>;
|
|
86
|
+
private nextId = 0;
|
|
87
|
+
|
|
88
|
+
constructor(size: number) {
|
|
89
|
+
const workerUrl = new URL("./parse-worker.ts", import.meta.url).href;
|
|
90
|
+
this.workers = [];
|
|
91
|
+
this.idle = [];
|
|
92
|
+
this.queue = [];
|
|
93
|
+
this.pending = new Map();
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < size; i++) {
|
|
96
|
+
const worker = new Worker(workerUrl, { smol: true });
|
|
97
|
+
worker.onmessage = (event) => this.onWorkerMessage(worker, event.data);
|
|
98
|
+
worker.onerror = (event) => this.onWorkerError(worker, event);
|
|
99
|
+
this.workers.push(worker);
|
|
100
|
+
this.idle.push(worker);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Number of workers in the pool. */
|
|
105
|
+
get size(): number {
|
|
106
|
+
return this.workers.length;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private onWorkerMessage(worker: Worker, data: any): void {
|
|
110
|
+
const task = this.pending.get(worker);
|
|
111
|
+
this.pending.delete(worker);
|
|
112
|
+
|
|
113
|
+
if (data.type === "error") {
|
|
114
|
+
task?.reject(new Error(data.error));
|
|
115
|
+
} else {
|
|
116
|
+
task?.resolve(data);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.dispatchNext(worker);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private onWorkerError(worker: Worker, event: Event | ErrorEvent): void {
|
|
123
|
+
const task = this.pending.get(worker);
|
|
124
|
+
this.pending.delete(worker);
|
|
125
|
+
const msg = event instanceof ErrorEvent ? event.message : "Worker error";
|
|
126
|
+
task?.reject(new Error(msg));
|
|
127
|
+
this.dispatchNext(worker);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Send the next queued task to a now-idle worker, or mark it idle. */
|
|
131
|
+
private dispatchNext(worker: Worker): void {
|
|
132
|
+
const next = this.queue.shift();
|
|
133
|
+
if (next) {
|
|
134
|
+
this.pending.set(worker, { resolve: next.resolve, reject: next.reject });
|
|
135
|
+
worker.postMessage(next.message);
|
|
136
|
+
} else {
|
|
137
|
+
this.idle.push(worker);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Send a message to an available worker, or queue it. */
|
|
142
|
+
private dispatch(message: any): Promise<any> {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const worker = this.idle.pop();
|
|
145
|
+
if (worker) {
|
|
146
|
+
this.pending.set(worker, { resolve, reject });
|
|
147
|
+
worker.postMessage(message);
|
|
148
|
+
} else {
|
|
149
|
+
this.queue.push({ message, resolve, reject });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Parse classes from a header file using a worker thread.
|
|
156
|
+
* Automatically retries with fallback pre-includes if initial parse finds nothing.
|
|
157
|
+
*/
|
|
158
|
+
async parseClasses(headerPath: string, targets: string[], fallbackPreIncludes?: string[]): Promise<ClassParseResult> {
|
|
159
|
+
const result = await this.dispatch({
|
|
160
|
+
id: this.nextId++,
|
|
161
|
+
type: "parse-classes",
|
|
162
|
+
headerPath,
|
|
163
|
+
targets,
|
|
164
|
+
fallbackPreIncludes
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
classes: new Map(result.classes),
|
|
168
|
+
targets: result.targets
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Parse protocols from a header file using a worker thread.
|
|
174
|
+
* Automatically retries with fallback pre-includes if initial parse finds nothing.
|
|
175
|
+
*/
|
|
176
|
+
async parseProtocols(
|
|
177
|
+
headerPath: string,
|
|
178
|
+
targets: string[],
|
|
179
|
+
fallbackPreIncludes?: string[]
|
|
180
|
+
): Promise<ProtocolParseResult> {
|
|
181
|
+
const result = await this.dispatch({
|
|
182
|
+
id: this.nextId++,
|
|
183
|
+
type: "parse-protocols",
|
|
184
|
+
headerPath,
|
|
185
|
+
targets,
|
|
186
|
+
fallbackPreIncludes
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
protocols: new Map(result.protocols),
|
|
190
|
+
targets: result.targets
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Parse enums from a header file using a worker thread.
|
|
196
|
+
* Extracts both integer enums (NS_ENUM/NS_OPTIONS) and string enums
|
|
197
|
+
* (NS_TYPED_EXTENSIBLE_ENUM) in a single clang pass.
|
|
198
|
+
*/
|
|
199
|
+
async parseEnums(
|
|
200
|
+
headerPath: string,
|
|
201
|
+
integerTargets: string[],
|
|
202
|
+
stringTargets: string[],
|
|
203
|
+
fallbackPreIncludes?: string[]
|
|
204
|
+
): Promise<EnumParseResult> {
|
|
205
|
+
const result = await this.dispatch({
|
|
206
|
+
id: this.nextId++,
|
|
207
|
+
type: "parse-enums",
|
|
208
|
+
headerPath,
|
|
209
|
+
integerTargets,
|
|
210
|
+
stringTargets,
|
|
211
|
+
fallbackPreIncludes
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
integerEnums: new Map(result.integerEnums),
|
|
215
|
+
stringEnums: new Map(result.stringEnums),
|
|
216
|
+
integerTargets: result.integerTargets,
|
|
217
|
+
stringTargets: result.stringTargets
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Parse classes, protocols, and enums from a single header in one clang invocation.
|
|
223
|
+
* This avoids spawning multiple clang processes for the same header file.
|
|
224
|
+
*/
|
|
225
|
+
async parseAll(
|
|
226
|
+
headerPath: string,
|
|
227
|
+
classTargets: string[],
|
|
228
|
+
protocolTargets: string[],
|
|
229
|
+
integerEnumTargets: string[],
|
|
230
|
+
stringEnumTargets: string[],
|
|
231
|
+
fallbackPreIncludes?: string[]
|
|
232
|
+
): Promise<UnifiedParseResult> {
|
|
233
|
+
const result = await this.dispatch({
|
|
234
|
+
id: this.nextId++,
|
|
235
|
+
type: "parse-all",
|
|
236
|
+
headerPath,
|
|
237
|
+
classTargets,
|
|
238
|
+
protocolTargets,
|
|
239
|
+
integerEnumTargets,
|
|
240
|
+
stringEnumTargets,
|
|
241
|
+
fallbackPreIncludes
|
|
242
|
+
});
|
|
243
|
+
return {
|
|
244
|
+
classes: new Map(result.classes),
|
|
245
|
+
protocols: new Map(result.protocols),
|
|
246
|
+
integerEnums: new Map(result.integerEnums),
|
|
247
|
+
stringEnums: new Map(result.stringEnums),
|
|
248
|
+
structs: new Map(result.structs ?? []),
|
|
249
|
+
structAliases: result.structAliases ?? [],
|
|
250
|
+
typedefs: new Map(result.typedefs ?? []),
|
|
251
|
+
functions: new Map(result.functions ?? [])
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Parse all targets from an entire framework using a single batched clang invocation.
|
|
257
|
+
* All framework headers are included in one temp .m file, reducing clang overhead
|
|
258
|
+
* from one process per header to one process per framework.
|
|
259
|
+
*
|
|
260
|
+
* @param headerPaths - All header file paths for this framework
|
|
261
|
+
* @param classTargets - All class names to extract
|
|
262
|
+
* @param protocolTargets - All protocol names to extract
|
|
263
|
+
* @param integerEnumTargets - All integer enum names to extract
|
|
264
|
+
* @param stringEnumTargets - All string enum names to extract
|
|
265
|
+
* @param preIncludes - Pre-include headers for macro expansion (e.g., Foundation/Foundation.h)
|
|
266
|
+
*/
|
|
267
|
+
async parseBatch(
|
|
268
|
+
headerPaths: string[],
|
|
269
|
+
classTargets: string[],
|
|
270
|
+
protocolTargets: string[],
|
|
271
|
+
integerEnumTargets: string[],
|
|
272
|
+
stringEnumTargets: string[],
|
|
273
|
+
preIncludes: string[],
|
|
274
|
+
frameworkName?: string
|
|
275
|
+
): Promise<UnifiedParseResult> {
|
|
276
|
+
const result = await this.dispatch({
|
|
277
|
+
id: this.nextId++,
|
|
278
|
+
type: "parse-batch",
|
|
279
|
+
headerPaths,
|
|
280
|
+
classTargets,
|
|
281
|
+
protocolTargets,
|
|
282
|
+
integerEnumTargets,
|
|
283
|
+
stringEnumTargets,
|
|
284
|
+
preIncludes,
|
|
285
|
+
frameworkName
|
|
286
|
+
});
|
|
287
|
+
return {
|
|
288
|
+
classes: new Map(result.classes),
|
|
289
|
+
protocols: new Map(result.protocols),
|
|
290
|
+
integerEnums: new Map(result.integerEnums),
|
|
291
|
+
stringEnums: new Map(result.stringEnums),
|
|
292
|
+
structs: new Map(result.structs ?? []),
|
|
293
|
+
structAliases: result.structAliases ?? [],
|
|
294
|
+
typedefs: new Map(result.typedefs ?? []),
|
|
295
|
+
functions: new Map(result.functions ?? [])
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Terminate all workers and clean up resources. */
|
|
300
|
+
destroy(): void {
|
|
301
|
+
for (const worker of this.workers) {
|
|
302
|
+
worker.terminate();
|
|
303
|
+
}
|
|
304
|
+
this.workers = [];
|
|
305
|
+
this.idle = [];
|
|
306
|
+
this.queue = [];
|
|
307
|
+
this.pending.clear();
|
|
308
|
+
}
|
|
309
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "objcjs-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Auto-generated TypeScript type declarations for macOS Objective-C frameworks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,14 +29,24 @@
|
|
|
29
29
|
"./*": {
|
|
30
30
|
"types": "./dist/*/index.d.ts",
|
|
31
31
|
"default": "./dist/*/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./*/functions": {
|
|
34
|
+
"types": "./dist/*/functions.d.ts",
|
|
35
|
+
"default": "./dist/*/functions.js"
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
"files": [
|
|
35
|
-
"dist"
|
|
39
|
+
"dist",
|
|
40
|
+
"generator",
|
|
41
|
+
"bin"
|
|
36
42
|
],
|
|
43
|
+
"bin": {
|
|
44
|
+
"objcjs-types": "./bin/objcjs-types.ts"
|
|
45
|
+
},
|
|
37
46
|
"scripts": {
|
|
38
47
|
"clear": "rm -rf src && rm -rf dist",
|
|
39
48
|
"generate": "bun run generator/index.ts",
|
|
49
|
+
"generate:custom": "bun run generator/custom.ts",
|
|
40
50
|
"build": "bun run clear && bun run generate && bunx tsgo",
|
|
41
51
|
"format": "prettier --write ."
|
|
42
52
|
},
|
|
@@ -59,7 +69,10 @@
|
|
|
59
69
|
"devDependencies": {
|
|
60
70
|
"@types/bun": "latest",
|
|
61
71
|
"@typescript/native-preview": "^7.0.0-dev.20260218.1",
|
|
62
|
-
"objc-js": "^1.1
|
|
72
|
+
"objc-js": "^1.2.1",
|
|
63
73
|
"prettier": "^3.8.1"
|
|
64
|
-
}
|
|
74
|
+
},
|
|
75
|
+
"trustedDependencies": [
|
|
76
|
+
"objc-js"
|
|
77
|
+
]
|
|
65
78
|
}
|