-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Unify iOS initialization / Add support for 0.72.0-rc.5 #4523
Conversation
Any ETA for when this PR will land in a release? |
@MicroDroid Probably next week, before 0.72 stable is released. |
If I modify my package.json to pull the package straight from this pull request, would the package work? I have no idea how pods work, not sure if something needs to be published somewhere |
@MicroDroid Yeah, you can install Reanimated from a particular commit, here's the command:
|
ok cool, I see you made a bunch of commits just minutes ago, let me know when you think I should try upgrade It's kinda blocking my work hence I am going fast at trying it out |
I noticed that you, here, added I do not use Fabric, and I am using RN 0.72.0-rc.5 (confirmed by |
@MicroDroid |
Tried exactly that now, but still no go. I don't feel like it's a caching issue... not sure what it could be though. |
I |
More interestingly, I went ahead and:
... and still the same error |
@MicroDroid What happens if you run |
|
@MicroDroid Indeed, there's no |
Not sure honestly, I did |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No super happy we are adding more version specific code. Let's get this one in but we should seriously consider either unifying initialization logic (it appears like we still use the same jsExecutorFactorySth method, so it looks like we should be able to do this across older versions too), or dropping support for older RN releases
Class cls = [self class]; | ||
Method originalMethod = class_getInstanceMethod(cls, @selector(jsExecutorFactoryForBridge:)); | ||
Method swizzledMethod = class_getInstanceMethod(cls, @selector(swizzled_jsExecutorFactoryForBridge:)); | ||
method_exchangeImplementations(originalMethod, swizzledMethod); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this still work if the instance doesn't implement jsExecutorFactoryForBridge:
?
I wonder if you should use something like this: https://github.com/microsoft/react-native-test-app/blob/2e5d0e64535230ed07a4ecf398387057b34b7121/ios/ReactTestApp/React%2BCompatibility.m#L7
I think you should also use a different prefix for the method name to avoid potential name clashes, e.g. reanimated_jsExecutorFactoryForBridge
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @tido64, thanks for the review! I've renamed the prefix in d018b2c as you suggested 😄
As for the case when jsExecutorFactoryForBridge:
is not implemented, indeed we don't handle it properly now but it looks like since 0.72.0-rc.4 this is always implemented in RCTAppDelegate
, at least in greenfield apps. We could add a conditional on didAddMethod
as you proposed but I'm not sure what would happen in the line where we call [self reanimated_jsExecutorFactoryForBridge:bridge]
then. I need some more time to learn and think about it.
Thanks for the code pointer to RTASwizzleSelector
, would you mind if we copy it to Reanimated codebase as a utility function? We also need to swizzle some methods of ViewControllers for Shared Element Transitions.
Finally, I'm curious to know what's the reason to use +initialize
here, I found one article that recommends to perform swizzling always in +load
method (link).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the code pointer to
RTASwizzleSelector
, would you mind if we copy it to Reanimated codebase as a utility function? We also need to swizzle some methods of ViewControllers for Shared Element Transitions.
Feel free to copy it 👍
Finally, I'm curious to know what's the reason to use
+initialize
here, I found one article that recommends to perform swizzling always in+load
method (link).
The reason behind using +initialize
is because we only want to swizzle if the class is actually used. In our case, race condition isn't really a concern since React doesn't initialize modules on multiple threads (at least not currently). We also use dispatch_once
here so its atomicity is ensured.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to copy it 👍
Thanks!
The reason behind using
+initialize
is because we only want to swizzle if the class is actually used. In our case, race condition isn't really a concern since React doesn't initialize modules on multiple threads (at least not currently). We also usedispatch_once
here so its atomicity is ensured.
That makes perfect sense, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this still work if the instance doesn't implement
jsExecutorFactoryForBridge:
?
@tido64 I'm still thinking about this question. Our aim is to automatically initialize Reanimated for greenfield apps; if some custom app doesn't use RCTAppDelegate
, it needs to perform initialization on its own. It looks like since 0.72.0-rc.4 jsExecutorFactoryForBridge:
is already implemented in RCTAppDelegate (link). This means that we can assume that didAddMethod
returns false
which reduces to the code that we currently have in this PR, or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell, Reanimated only needs to swizzle jsExecutorFactoryForBridge:
on Paper. Is that correct? RCTAppDelegate
is only available from 0.70. In this version, jsExecutorFactoryForBridge:
is only implemented iff New Architecture is enabled. I don't know how far back you intend to support, but if you follow React Native's support window, this change will:
- Break 0.69 because
RCTAppDelegate
is missing - Break 0.70 because
jsExecutorFactoryForBridge:
is not implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell, Reanimated only needs to swizzle
jsExecutorFactoryForBridge:
on Paper. Is that correct?
Yes, that's correct. This PR introduces swizzling in RCTAppDelegate
only for React Native 0.72+:
#if REACT_NATIVE_MINOR_VERSION >= 72 && !defined(RCT_NEW_ARCH_ENABLED) && !defined(DONT_AUTOINSTALL_REANIMATED) |
For 0.71 and older we will still use the old way which is to implement jsExecutorFactoryForBridge:
method for UIResponder
:
#if REACT_NATIVE_MINOR_VERSION <= 71 && !defined(RCT_NEW_ARCH_ENABLED) && !defined(DONT_AUTOINSTALL_REANIMATED) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I missed this part. Then it looks like you should be good for now.
Is it already available on npm? |
Yes, you can install nightly build with |
@MicroDroid Here's the issue for the problem that you've mentioned: #4553 |
## Summary Recently, we began rewriting the iOS initialization process. Tomek moved the injection of JSI bindings to `installTurboModule` (check out #4523) and then used swizzling to eliminate the `REAEventDispatcher` (see #4576). More context here: expo/expo#23057 (comment) This PR removed UIManager overriding by swizzling the `_manageChildren` and `uiBlockWithLayoutUpdateForRootView` methods. These changes allowed us to get rid of `RCTAppDelegate+Reanimated`, `UIResponder+Reanimated`, and `REAInitializer`. For now, we're gonna leave `REAInitializer` as deprecated for a few more releases to maintain backward compatibility. But just a heads up, we'll be removing it in the future. ## Test plan - I tested building on different react native versions by CI https://github.com/piaskowyk/reanimated-rn-version-tester/actions/runs/5375166823 - Tested Layout Animations on Example app form main branch.
## Summary Recently, we began rewriting the iOS initialization process. Tomek moved the injection of JSI bindings to `installTurboModule` (check out #4523) and then used swizzling to eliminate the `REAEventDispatcher` (see #4576). More context here: expo/expo#23057 (comment) This PR removed UIManager overriding by swizzling the `_manageChildren` and `uiBlockWithLayoutUpdateForRootView` methods. These changes allowed us to get rid of `RCTAppDelegate+Reanimated`, `UIResponder+Reanimated`, and `REAInitializer`. For now, we're gonna leave `REAInitializer` as deprecated for a few more releases to maintain backward compatibility. But just a heads up, we'll be removing it in the future. ## Test plan - I tested building on different react native versions by CI https://github.com/piaskowyk/reanimated-rn-version-tester/actions/runs/5375166823 - Tested Layout Animations on Example app form main branch.
Summary
This PR bumps react-native to 0.72.0-rc.5 and slightly changes initialization path on iOS (Paper) due to recent changes in the framework (facebook/react-native#37523).
Fixes #4521. Continues #3005.
On iOS, Reanimated needs to overwrite two React internal modules:
RCTUIManager
→REAUIManager
to interceptmanageChildren
calls in order to observe React tree changes (here)RCTEventDispatcher
→REAEventDispatcher
to intercept events in the native code while still on the UI thread (here)The rest of the initialization (injecting JSI bindings) can be safely done in
installTurboModule
method, as we already do on Android and iOS/Fabric.Previously, this was done using categories (see
UIResponder+Reanimated.mm
), in particular by overwritingjsExecutorFactoryForBridge:
method which is called during initialization. However, since 0.72.0-rc.4, this method is already implemented inRCTAppDelegate
so the trick does no longer work, making the app fail with the following error "[Reanimated] The native part of Reanimated doesn't seem to be initialized".In this PR, I've used a category to overwrite another method
extraModulesForBridge
which swizzles the implementation ofjsExecutorFactoryForBridge
method and runsREAInitializer
before the original call.As suggested by @kmagiera, alternatively we could just swizzle the methods of
RCTUIManager
andRCTEventDispatcher
.TODO
Test plan