Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ios][core] Create JSI host object to access the new expo modules #15847

Merged
merged 6 commits into from
Jan 11, 2022

Conversation

tsapeta
Copy link
Member

@tsapeta tsapeta commented Jan 8, 2022

Why

Step further to integrate our new modules architecture with JSI and running native functions synchronously.

How

  • Created ExpoModulesHostObject that installs into the runtime as global.ExpoModules. The host object is an abstract object where we can define our own property getter, allowing us to create JS object for each module lazily.
  • Since Swift doesn't understand C++ symbols, it has no direct access to JSI API. To work around this, I created another layer of abstraction to JavaScript runtime (JavaScriptRuntime) and objects (JavaScriptObject) in ObjC++ to expose some functionalities to Swift.
  • Added isAsync property to sweet functions and runSynchronously() modifier.
  • AppContext can now access the runtime, so the modules can access the runtime too!

Further plans

  • Make expo-random to be an expo module and be the first to use synchronous functions.
  • Right now all new modules can be accessed from the host object, however at the beginning we'd like to enable it only for some subset of modules (especially that events are not supported yet). Maybe useJavaScriptRuntime(true) definition component?
  • Cleanup stuff related to ExpoModulesProxySpec TurboModule which was an alternative implementation to NativeModulesProxy — the solution with host object is much better, more scalable and that lets us do much more magic in the future, so we can drop the TurboModule (it wasn't used by default anyway).
  • Ensure that JavaScriptRuntime and JavaScriptObject are thread-safe (another addition to bare JSI objects).
  • Move some responsibility of calling exported functions from ModuleHolder to the respective functions. See my comment in JavaScriptUtils.swift
  • Switch modules to use requireNativeModule(moduleName) instead of NativeModulesProxy (see related PR [core] Add requireNativeModule function #15848). We also need to replace JS examples in the docs to use this new function because we don't want to introduce developers into the proxy concept.

Test Plan

  • Used expo-haptics with a new synchronous function as a playground, all worked as expected.
  • Reloading the app works fine (it would crash on JSCRuntime deallocation if there are any unreleased objects)

@expo-bot expo-bot added the bot: suggestions ExpoBot has some suggestions label Jan 8, 2022
@tsapeta tsapeta marked this pull request as ready for review January 9, 2022 19:22
packages/expo-modules-core/ios/JSI/JavaScriptObject.mm Outdated Show resolved Hide resolved
block:(nonnull JSAsyncFunctionBlock)block
{
jsi::Function function = [_runtime createAsyncFunction:name argsCount:argsCount block:block];
_jsObjectPtr->setProperty(*[_runtime get], [name UTF8String], function);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shoulda check if runtime is not null

Copy link
Contributor

@bbarthec bbarthec Jan 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shoulda check if runtime is not null

Do you? You marked runtime as nonnullable above in the same file 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nonnull only when it's passed as an argument to the initializer of JavaScriptObject. _runtime is marked as a __weak property, so it might be deallocated before the object gets deallocated.

packages/expo-modules-core/ios/JSI/JavaScriptRuntime.mm Outdated Show resolved Hide resolved
Copy link
Contributor

@bbarthec bbarthec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good, even though I'm not accustomed to reading the mix of ObjC(++)/Swift code tangled together in one PR 😅
I've got one question - can you be even more explicit in regards to nullability in ObjC(++). You've annotated majority of the code, but there're some gaps (especially in functions params definitions).

packages/expo-modules-core/ios/JSI/JavaScriptObject.mm Outdated Show resolved Hide resolved
block:(nonnull JSAsyncFunctionBlock)block
{
jsi::Function function = [_runtime createAsyncFunction:name argsCount:argsCount block:block];
_jsObjectPtr->setProperty(*[_runtime get], [name UTF8String], function);
Copy link
Contributor

@bbarthec bbarthec Jan 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shoulda check if runtime is not null

Do you? You marked runtime as nonnullable above in the same file 🤔

packages/expo-modules-core/ios/JSI/ExpoModulesHostObject.h Outdated Show resolved Hide resolved
packages/expo-modules-core/ios/JSI/ExpoModulesHostObject.h Outdated Show resolved Hide resolved
ExpoModulesHostObject::ExpoModulesHostObject(SwiftInteropBridge *swiftInterop) : swiftInterop(swiftInterop) {}

ExpoModulesHostObject::~ExpoModulesHostObject() {
[swiftInterop setRuntime:nil];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it intentionally to setRuntime:nil here? what if someone do global.ExpoModules = null; in javascript and after GC, will it also call to here and cleanup the runtime?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's intentional. Our JavaScriptRuntime wrapper holds the reference to jsi::Runtime and shared pointer to react::CallInvoker and so we should make sure they both can deallocate when the JS engine wants it.

Well, if you do global.ExpoModules = null then you actually opt into some unexpected behavior 🤪
We can also prevent this case with such JS code:

const ExpoModules = global.ExpoModules;
delete global.ExpoModules;
// And now use `ExpoModules` instead of `global.ExpoModules`.

This shouldn't deallocate the object 🤔 I can check out how that works in #15848

Copy link
Contributor

@Kudo Kudo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@tsapeta tsapeta force-pushed the @tsapeta/ios/modules-host-object branch from 43121bf to 2c15564 Compare January 11, 2022 10:05
@expo-bot expo-bot added bot: passed checks ExpoBot has nothing to complain about and removed bot: suggestions ExpoBot has some suggestions labels Jan 11, 2022
@tsapeta tsapeta merged commit 6ad80de into master Jan 11, 2022
@tsapeta tsapeta deleted the @tsapeta/ios/modules-host-object branch January 11, 2022 10:09
@tsapeta tsapeta changed the title [ios][core] Make JSI host object for accessing the new modules [ios][core] Create JSI host object to access the new expo modules Jan 11, 2022
lukmccall added a commit to software-mansion-labs/expo-maps that referenced this pull request Jan 27, 2022
Bump `expo-modules-core` to `0.7.0`:

# Changelog:

## 0.7.0 — 2022-01-26

### 🎉 New features

- Allow accessing `RCTBridge` from the modules on iOS. ([#15816](expo/expo#15816) by [@tsapeta](https://github.com/tsapeta))
- Added support for native callbacks through the view props in Sweet API on iOS. ([#15731](expo/expo#15731) by [@tsapeta](https://github.com/tsapeta))
- Added support for native callbacks through the view props in Sweet API on Android. ([#15743](expo/expo#15743) by [@lukmccall](https://github.com/lukmccall))
- The `ModuleDefinition` will use class name if the `name` component wasn't provided in Sweet API on Android. ([#15738](expo/expo#15738) by [@lukmccall](https://github.com/lukmccall))
- Added `onViewDestroys` component to the `ViewManager` in Sweet API on Android. ([#15740](expo/expo#15740) by [@lukmccall](https://github.com/lukmccall))
- Added shortened `constants` component that takes `vargs Pair<String, Any?>` as an argument in Sweet API on Android. ([#15742](expo/expo#15742) by [@lukmccall](https://github.com/lukmccall))
- Introduced the concept of chainable exceptions in Sweet API on iOS. ([#15813](expo/expo#15813) by [@tsapeta](https://github.com/tsapeta))
- Sweet function closures can throw errors on iOS. ([#15849](expo/expo#15849) by [@tsapeta](https://github.com/tsapeta))
- Add `requireNativeModule` function to replace accessing native modules from `NativeModulesProxy`. ([#15848](expo/expo#15848) by [@tsapeta](https://github.com/tsapeta))
- Implemented basic functionality of JSI host object to replace `NativeModulesProxy` on iOS. ([#15847](expo/expo#15847) by [@tsapeta](https://github.com/tsapeta))

### 🐛 Bug fixes

- It's no longer possible to directly call methods from the `ModuleDefinition` in the `ViewManagers` on Android. ([#15741](expo/expo#15741) by [@lukmccall](https://github.com/lukmccall))
- Fix compatibility with react-native 0.66. ([#15914](expo/expo#15914) by [@Kudo](https://github.com/kudo))

## 0.6.4 — 2022-01-05

### 🐛 Bug fixes

- Fix `ReactInstanceManager.onHostPause` exception from moving Android apps to background. ([#15748](expo/expo#15748) by [@Kudo](https://github.com/kudo))

## 0.6.3 — 2021-12-16

### 🐛 Bug fixes

- Fixed the deep link wasn't passed to the application if the application wasn't running when the deep link was sent. ([#15593](expo/expo#15593) by [@lukmccall](https://github.com/lukmccall))

## 0.6.2 — 2021-12-15

### 🎉 New features

- Add `onNewIntent` and `onBackPressed` support to `ReactActivityLifecycleListener`. ([#15550](expo/expo#15550) by [@Kudo](https://github.com/Kudo))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bot: passed checks ExpoBot has nothing to complain about
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants