npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-native-native-runtime

v0.2.0

Published

Access the Obj-C runtime from React Native via JSI!

Downloads

3

Readme

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!

# WARNING: only tested with react-native@^0.64.2. Will not work on lower versions; *may* work on newer versions.

# Install the npm package
npm install --save react-native-native-runtime

# Install the Cocoapod
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.

// Class lookup:
// Returns a JS HostObject wrapping a native class.
// This works via the Obj-C runtime helper NSClassFromString, so it has O(1) complexity.
objc.NSString;

// Protocol lookup:
// Returns a JS HostObject wrapping a native Protocol (if a class with the same name wasn't found first).
// This works via the Obj-C runtime helper NSProtocolFromString, so it has O(1) complexity.
objc.NSSecureCoding;

// Constant/variable lookup:
// Looks up the `NSStringTransformLatinToHiragana` variable from the executable (if neither a class
// nor a protocol with the same name was found first).
// This works via dlsym, so I believe it has O(N) complexity, but probably isn't too slow anyway.
objc.NSStringTransformLatinToHiragana;

// Selector lookup:
// Returns a JS HostObject wrapping a native Selector.
// This works via the Obj-C runtime helper NSSelectorFromString, so it has O(1) complexity.
// I can't think of a good example for this, but it's possible.
objc.getSelector("NoGoodExample:soWhoKnows:");

// Will return an array of all Obj-C classes and all convenience methods, but that's all.
// Does not, for example, list out all constants/variables/protocols available. Those have to be
// looked up individually.
Object.keys(objc);

// Will return the string:
// [object HostObjectObjc]
objc.toString();

// Will cause an infinite loop and crash! Need some advice from JSI experts on this.
// It involves the following getters being called in sequence:
// $$typeof -> Symbol.toStringTag -> toJSON -> Symbol.toStringTag -> 
//   Symbol.toStringTag -> Symbol.toStringTag -> Symbol.toStringTag -> 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).

// Initialise an NSString
const hello: objc.NSString =
  objc.NSString.alloc()['initWithString:']('Hello');

// Return a new NSString by concatenating it 
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:

// Marshal the NSString to a JS primitive string
console.log(helloWorld.toJS());

Beyond that, you can get the keys on the class instance:

// Will return a list of all instance variables, properties, and methods, and some methods
// like toString().
// TODO: list out all the *inherited* instance variables, properties, and methods as well.
Object.keys(helloWorld);

You can also use getters:

// Allocate a native class instance
const utterance: objc.AVSpeechUtterance =
  objc.AVSpeechUtterance.alloc()['initWithString:']('Hello, world!');

// Get the property
utterance.voice;

... and call setters:

// Allocate a native class instance
const utterance: objc.AVSpeechUtterance =
  objc.AVSpeechUtterance.alloc()['initWithString:']('Hello, world!');

// Set properties on it
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