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 |
kmagiera
left a comment
There was a problem hiding this comment.
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.
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.
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.
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
+initializehere, I found one article that recommends to perform swizzling always in+loadmethod (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.
Feel free to copy it 👍
Thanks!
The reason behind using
+initializeis 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_oncehere so its atomicity is ensured.
That makes perfect sense, thanks!
There was a problem hiding this comment.
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.
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
RCTAppDelegateis missing - Break 0.70 because
jsExecutorFactoryForBridge:is not implemented.
There was a problem hiding this comment.
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+:
For 0.71 and older we will still use the old way which is to implement jsExecutorFactoryForBridge: method for UIResponder:
There was a problem hiding this comment.
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→REAUIManagerto interceptmanageChildrencalls in order to observe React tree changes (here)RCTEventDispatcher→REAEventDispatcherto 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
installTurboModulemethod, 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 inRCTAppDelegateso 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
extraModulesForBridgewhich swizzles the implementation ofjsExecutorFactoryForBridgemethod and runsREAInitializerbefore the original call.As suggested by @kmagiera, alternatively we could just swizzle the methods of
RCTUIManagerandRCTEventDispatcher.TODO
Test plan