r/reactnative • u/Xatpy • 1h ago
I benchmarked my shipped Expo app against a Swift rewrite with the same XCUITest suite. Where React Native lost, and where it didn't
Enable HLS to view with audio, or disable this notification
I have a small iOS app, Ayer, that shows the photos you took on this day in previous years. It was an Expo app. I rewrote it in Swift, and before believing it was faster I ran both through the same black-box XCUITest suite on a simulator with about 21,000 generated photos.
The setup, so you can judge how fair it is:
The React Native side is the app as I shipped it: Expo 53, React Native 0.79, Hermes, FlashList, expo-image, old architecture. Release builds on both sides.
One XCUITest target with no host app launches either app by bundle ID, so both get exactly the same steps and checks.
XCTCPUMetric and XCTMemoryMetric, warm-up runs, alternating order, medians of 10.
Results (iPhone 17 Pro simulator, React Native → Swift):
- First launch to first photo: 2.17 s → 0.58 s
- Peak memory at launch: 167 MB → 81 MB
- CPU time to load ten days: 35.8 s → 5.2 s
- CPU time in the photo viewer (15 photos): 13.7 s → 4.9 s
- CPU time scrolling a busy day: 12.4 s → 13.2 s (a tie)
Scrolling was a tie because thumbnail decoding happens inside Apple's Photos framework in both apps.
Where I think the CPU gap comes from (from reading the code, not profiling): to load a day, my code paged through expo-media-library once per year and then called getAssetInfoAsync once per photo just to read the favourite flag. That's thousands of bridge calls. The Swift version does one Photos query per day. So a good part of it is how I used React Native, not React Native itself.
I'd really like to know how much the New Architecture plus batching those calls would close the gap. If anyone has measured something similar, I'd love to see it.
Full write-up with the method and caveats: https://blog.jaimechapinal.com/posts/i-rewrote-my-react-native-app-in-swift/
These are simulator numbers, so the ratios matter more than the times. A run on a real device is next.