react-native-native-runtime
Access the native APIs directly from the React Native JS context!
For now, we support just Objective-C (for iOS/macOS/tvOS devices, but I'll refer to iOS for brevity). Adding support for Java (for Android devices) is on my wishlist for the future.
Installation
Please get in contact if these instructions don't work for you!
npm install --save react-native-native-runtime
cd ios && pod install && cd ..
Usage
For now, as I haven't installed the package yet, just clone the repo and play around with example/src/App.tsx.
The Java runtime
🚧 I haven't yet started building this, and it's further away from my expertise (I'm more of an iOS developer). Get in contact if you'd like to get involved!
The Obj-C runtime
All Obj-C APIs are exposed through a proxy object called objc, which is injected into the JS context's global scope early at run time (provided you have remembered to install the Cocoapod and rebuilt your iOS app since then). Technically it occurs when React Native finds the setBridge method in ios/ObjcRuntime.mm just like for any other iOS native module and then calls the ObjcRuntimeJsi::install() method within.
TypeScript typings?
⚠️ First, a warning: We only have very minimal hand-written TypeScript typings at the moment. Get used to any type until we make a more lasting solution, most likely based on the NativeScript metadata/typings generator. For now, copy example/src/objc-types.d.ts to get started.
The objc proxy object
As mentioned, this is available in the global scope on any Apple app.
Generally, you'll use the objc proxy object to look up some native data type. If a match is found for the native data type, we wrap it in a C++ HostObject class instance that is shared across to the JS context.
objc.NSString;
objc.NSSecureCoding;
objc.NSStringTransformLatinToHiragana;
objc.getSelector("NoGoodExample:soWhoKnows:");
Object.keys(objc);
objc.toString();
console.log(objc);
Host Objects
Again, this is a C++ HostObject class instance that is shared across to the JS context. Don't ask me how the memory-management works! That's one for a JSI expert (and I'd love some code review on my approach).
The objc proxy object is one such HostObject. I've made some others:
- HostObjectClass (wraps a class)
- HostObjectClassInstance (wraps a class instance)
- HostObjectProtocol (wraps a protocol)
- HostObjectSelector (wraps a selector)
TODO: I'll likely make these expand a common abstract class. For now, they all directly extend facebook::jsi::HostObject.
These may expand in future, but the former two cover a huge API surface on their own. I'll focus on documenting those, as the latter two are largely empty skeletons.
HostObjectClass
You can obtain a HostObjectClass by looking up a class on the objc proxy object:
const nSStringClass: objc.NSString = objc.NSString;
You can also call class methods (AKA static methods, in other languages) on it:
const voice: objc.AVSpeechSynthesisVoice =
objc.AVSpeechSynthesisVoice['voiceWithLanguage:']('en-GB');
We'll cover what you can do with a class instance in the next section.
HostObjectClassInstance
Once you have a class instance, you can call instance methods. The method names mirror the Obj-C selector, hence you'll be seeing a lot of colons. The JS invocation takes as many arguments as the Obj-C selector suggests (each colon indicates one param).
const hello: objc.NSString =
objc.NSString.alloc()['initWithString:']('Hello');
const helloWorld: objc.NSString =
hello['stringByAppendingString:'](', world!');
You will have noticed that we're passing JS primitive types in as parameters. All JS primitive types are marshalled into equivalent Obj-C types:
- string -> NSString
- number -> NSNumber
- boolean -> NSBoolean
- Array -> NSArray
- object -> NSDictionary
- undefined -> nil
- null -> nil
Conversely, you can also marshal the following types from Obj-C to JS:
- NSString -> string
- NSNumber -> number
- NSBoolean -> boolean
- NSArray -> Array (provided each of the constituent values are marshal-friendly)
- NSDictionary -> object (provided each of the constituent values are marshal-friendly)
- kCFNull -> null
- nil -> undefined
Do so using the toJS() method on a HostObjectClassInstance:
console.log(helloWorld.toJS());
Beyond that, you can get the keys on the class instance:
Object.keys(helloWorld);
You can also use getters:
const utterance: objc.AVSpeechUtterance =
objc.AVSpeechUtterance.alloc()['initWithString:']('Hello, world!');
utterance.voice;
... and call setters:
const utterance: objc.AVSpeechUtterance =
objc.AVSpeechUtterance.alloc()['initWithString:']('Hello, world!');
utterance.voice =
objc.AVSpeechSynthesisVoice['voiceWithLanguage:']('ja-JP');
... but both getters and setters are currently very experimental and I need some help from an Obj-C expert to get them right.
Contributing
Get in touch on the #objc-runtime channel of the React Native JSI Discord, or send me a message on Twitter!
License
MIT