Objective-C from JavaScript
Use UIHelper to find buttons, enter text and scroll. Use ObjC when you want to call a native Objective-C method directly, such as reading a Foundation object. You write JavaScript; you do not need to write or compile Objective-C.
Choose where the method runs
| Where | API | Example |
|---|---|---|
| Inside SpringBoard | ObjC | ObjC.class("NSUUID").UUID() |
| Inside another app | RemoteObjC.forApp(bundleId) | app.class("NSBundle").mainBundle() |
Local means SpringBoard. Opening another app or selecting it in the inspector does not change the target of ObjC. You do not need to connect to another app for local calls. Both APIs are available directly in scripts without imports or the at. prefix.
To confirm the target:
console.log(ObjC.getInfo().processName); // "SpringBoard"Call methods directly in SpringBoard
Get a class, call its methods, and convert native strings with toJS():
const NSUUID = ObjC.class("NSUUID");
const uuid = NSUUID.UUID();
const text = uuid.UUIDString();
try {
console.log(text.toJS()); // A UUID string
} finally {
text.release();
uuid.release();
}You can also obtain SpringBoard's application object:
const SpringBoard = ObjC.class("SpringBoard");
const application = SpringBoard.sharedApplication();
try {
console.log(application.respondsTo("canOpenURL:"));
} finally {
application.release();
}The same style works with other classes already loaded in SpringBoard. Use ObjC.hasClass("ClassName") to check availability. Installing an app or library does not mean its classes exist in SpringBoard. Private classes and methods can differ between iOS versions.
Four words you need
| Word | Meaning | Example |
|---|---|---|
| Client | Chooses which process receives your calls | ObjC or RemoteObjC.forApp(bundleId) |
| Class | A native type that can create objects or provide class methods | NSString, the native string type |
| Object / proxy | A JavaScript handle to a native object | The string returned by stringWithString: |
| Selector | The exact name of a native method, including its colons | stringWithString: |
ObjCObject on reference pages is a label for returned objects. It is not a global to import or construct. Likewise, client and object in a signature are example variable names.
1. Make a native string and read its value
const text = ObjC.call("NSString", "stringWithString:", "Hello");
try {
console.log(text.toJS()); // "Hello": a normal JS string
console.log(text.call("length")); // 5: a normal JS number
} finally {
text.release();
}Read the first line as: ask the NSString class to run stringWithString: with "Hello" as its argument. The result is a native object handle. toJS() copies its string value into JavaScript. length already returns a number, so it needs no toJS().
release() releases your handle when finished. It does not close an app. Do not use that handle afterward. Script cleanup also releases references, but explicit cleanup avoids accumulation in long loops.
2. Call a method with more than one argument
const dictionary = ObjC.create("NSMutableDictionary");
try {
dictionary.call("setObject:forKey:", "Hello", "message");
console.log(dictionary.toJS()); // {message: "Hello"}
} finally {
dictionary.release();
}ObjC.create() allocates and initializes an object. setObject:forKey: takes two arguments in that order: the value, then its key. Each colon belongs to one argument. You must know which class or object provides the method; the bridge does not invent app methods.
3. Optional shorthand
These two calls are equivalent:
const dictionary = ObjC.create("NSMutableDictionary");
try {
dictionary.call("setObject:forKey:", "Hello", "message");
dictionary.setObject_forKey_("Hello", "message");
} finally {
dictionary.release();
}Replace selector colons with underscores. length() has no arguments and needs no underscore. The final underscore is optional: setObject_forKey("Hello", "message") also works. Use call("exact:selector:", ...) when you prefer exact native spelling or when a native method name collides with a helper such as get, set or release. A double underscore represents a literal underscore.
4. Read information inside another app
ObjC always addresses SpringBoard. To call inside Settings, open it and choose a remote client:
if (!openApp("com.apple.Preferences")) throw new Error("Could not open Settings");
const app = RemoteObjC.forApp("com.apple.Preferences");
console.log(app.getInfo().bundleId); // "com.apple.Preferences"
const bundle = app.class("NSBundle").mainBundle();
let identifier;
try {
identifier = bundle.bundleIdentifier();
console.log(identifier ? identifier.toJS() : null);
} finally {
if (identifier) identifier.release();
bundle.release();
}The remote client has the same class, call, create, out and other methods as ObjC. Creating it does not launch the app. The app must be running with its ApplesauceOS helper enabled; a suspended or blacklisted app may not answer.
Keep one client per app and reuse it. Objects belong to the client/session that created them: copy ordinary data with toJS() before passing it to another client. After an app restart, call app.reconnect() and obtain new object handles.
What comes back?
| Native result | JavaScript result | What to do |
|---|---|---|
| BOOL, integer, floating point | Boolean or number | Use directly |
| Integer outside the safe Number range | BigInt, or decimal string if BigInt is unavailable | Preserve precision; convert to a string before JSON serialization |
| NSString, NSArray, NSDictionary, NSNumber | Native object proxy | Use toJS() when you need ordinary JS data |
| Other native object | Native object proxy | Call its known methods; toJS() is not a universal object serializer |
| nil or void | null | Check before calling methods |
| Supported structure, such as CGRect | Plain object with named fields | Read x, y, width, height for CGRect |
Pass JavaScript null for native nil; the string "nil" is just text. get(key) and set(key, value) use native key-value coding. Assigning object.someProperty = value is unsupported; use set() or an exact native setter.
Errors and limits
If a class or selector is unavailable, calls throw. hasClass() and respondsTo() let you check support. signature() reports native type encodings; it is an advanced diagnostic, not a human description of what a method does.
Calls are synchronous and execute on the target's main thread. Keep them short. Remote timeouts stop waiting; they do not undo a method already executing. Do not automatically repeat an uncertain mutation.
Callbacks, non-null Objective-C blocks, variadic methods, unions and arbitrary pointer arguments are unsupported. Known C functions use C and RemoteC. Wrong native calls can crash the target process.
Find the method you need
| Task | Reference |
|---|---|
| Choose another app | RemoteObjC.forApp |
| Find a class / check availability | class, hasClass |
| Call a class or object method | ObjC.call, object.call |
| Create an object | create |
| Copy a native value into JS | toJS |
| Read or write a native property | get, set |
| Receive an NSError output parameter | out |
| Inspect method support | respondsTo, signature |
| Release a handle / session | release, close |
| Recover after an app restart | reconnect |
Each reference page has its own arguments, result and small example. Read the target native API's documentation too: an ApplesauceOS method explains how to call it, not the meaning of every method supplied by every app.