[iOS][Fabric] Image shows another Image's picture after its native view is recycled
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 45/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- ios, objective-c, react-native
Línea de trabajo
The bug is in RCTImageComponentView.mm and RCTImageResponseObserverProxy.mm. Start by reading the linked lines to understand the view recycling and async callback flow. The reproducer app shows the problem; run it on iOS to see the wrong images. A fix likely involves creating a new observer proxy per subscription and checking the observer pointer in callbacks. Look at the fromObserver: argument and the _imageResponseObserverProxy lifecycle.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Description
On iOS with the new architecture, an <Image> sometimes shows the picture of a different <Image> that was unmounted just before. It is intermittent, and the component's other content (text etc.) is correct. I think this is the same thing people describe in #51764 ("displays local images in disorder", closed without a reproducer).
In our app it showed up in a list of section headers: one header got the icon and button glyph of another header that had mounted briefly while loading and then went away. It only happened on some app opens.
As far as I can tell this is a race between view recycling and the async image callback:
RCTImageResponseObserverProxy::didReceiveImagehops to the main queue before calling the view (RCTImageResponseObserverProxy.mm#L22-L31).RCTImageComponentViewcreates one proxy for its whole lifetime (L41). OnprepareForRecycleit unsubscribes and clears the image (L132-L137), and because deletes are applied before creates, the same view can be handed to a new<Image>in the same transaction.- If the old request finished on a background thread and its block is already waiting on the main queue, that block runs after the view has been reused.
didReceiveImageonly checks that there is a_state(L141-L147), not that the image belongs to the current request, so the old image is applied. - When the new source is already cached, its image is applied synchronously during mount (the coordinator replays a completed response on subscribe), so the late old image comes after it and stays on screen.
Unsubscribing does cancel the old request when nobody else is listening, but that is too late once the completion has already been dispatched.
A possible fix: create a new RCTImageResponseObserverProxy per subscription in _setStateAndResubscribeImageResponseObserver, and ignore callbacks where observer != _imageResponseObserverProxy.get() (in didReceiveImage, didReceiveProgress and didReceiveFailure). The fromObserver: argument is already passed in but not used. Comparing raw pointers could in theory hit a reused address, so a generation counter stored in the proxy and checked on the main thread might be safer. Happy to open a PR if that direction makes sense.
Steps to reproduce
- Run the reproducer app on an iPhone. The whole thing is in
ReproducerApp/App.tsxand only usesreact-native; the same code is also available as a Snack. - Wait for "Start" to become enabled (it prefetches the green image), then tap Start. A run is 50 rounds, about a minute.
- Each round shows a grid of 64 red images (96x96, unique URLs, not cached). As soon as the first one has loaded, all of them are replaced with a green image (64x64, cached), so the remaining red loads finish around the swap. The red and green images are different elements, so the red views are recycled and reused by the green ones.
- Watch the counters and the grid during the green phase.
Expected: every tile is green during the green phase and the counters stay at 0.
Actual: some tiles stay red during the green phase. A wrong image is counted when a green tile's onLoad reports the size of the red image (96 wide) instead of 64, which means the red image was delivered to the recycled view. On an iPhone, one run of 50 rounds had a wrong image in 32 rounds (64%), 111 wrong images in total, and the last round ended with four red tiles on screen. In the iOS simulator (Release build, iPhone 17 Pro Max, iOS 26.5) a run had 39 of 50 rounds with a wrong image (78%), 323 wrong images. The same code on Android: 0 of 50.
To make it happen almost every time (for debugging), delay the delivery by a random amount, for example replace the RCTExecuteOnMainQueue in RCTImageResponseObserverProxy::didReceiveImage with dispatch_after(..., arc4random_uniform(800) * NSEC_PER_MSEC, dispatch_get_main_queue(), ...). We did the equivalent with a swizzle on RCTImageComponentView's didReceiveImage:metadata:fromObserver: in our app (RN core is prebuilt there): the wrong image appeared in 6 of 6 runs, and 0 of 6 once the affected images used a component that ignores stale loads.
React Native Version
0.87.1 (reproducer repo). We first hit it in our app on 0.86.3. The relevant code is unchanged on main.
Affected Platforms
Runtime - iOS (the same reproducer on Android shows 0 wrong images in 50 rounds)
Areas
Fabric - The New Renderer
Output of npx @react-native-community/cli info
System:
OS: macOS 26.6.2
CPU: (10) arm64 Apple M1 Max
Memory: 167.31 MB / 32.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 25.6.0
path: /Users/mattijs/.local/share/mise/installs/node/latest/bin/node
Yarn: Not Found
npm:
version: 11.8.0
path: /Users/mattijs/.local/share/mise/installs/node/latest/bin/npm
Watchman:
version: 2026.07.27.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.16.2
path: /Users/mattijs/.local/share/mise/installs/ruby/3.4/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 25.5
- iOS 26.5
- macOS 26.5
- tvOS 26.5
- visionOS 26.5
- watchOS 26.5
Android SDK: Not Found
IDEs:
Android Studio: 2026.1 AI-261.23567.138.2611.15646644
Xcode:
version: 26.6/17F113
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.18
path: /usr/bin/javac
Ruby:
version: 3.4.8
path: /Users/mattijs/.local/share/mise/installs/ruby/3.4/bin/ruby
npmPackages:
"@react-native-community/cli":
installed: 20.2.0
wanted: 20.2.0
react:
installed: 19.2.3
wanted: 19.2.3
react-native:
installed: 0.87.1
wanted: 0.87.1
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
Stacktrace or Logs
No crash or log output. The wrong image is displayed silently.
MANDATORY Reproducer
https://github.com/mattijsf/rn-image-recycled-view-stale-image
Same code as a Snack: https://snack.expo.dev/@mattijsf/image-recycle-stale-load-ios?platform=ios
Screenshots and Videos
After a run of 50 rounds. The red tiles in the green grid are the wrong images.
| iPhone | iOS simulator | Android |
|---|---|---|
![]() |
![]() |
![]() |
| 32 of 50 rounds with a wrong image | 39 of 50 | 0 of 50 |
- Lenguaje dominante
- C++
- Estrellas
- 127k
- Forks
- 25.3k
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de react/react-native
-
Needs: Author Feedback Needs: Repro
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
react/react-native#58659 · 1 comentario ·
-
Needs: Author Feedback Needs: Repro
Dificultad 1/5 Menos de una hora Aptitud para principiantes 92/100
react/react-native#58621 · 1 comentario ·
-
Needs: Author Feedback Needs: Repro
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
react/react-native#58610 · 1 comentario ·
-
Needs: Triage :mag:
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
react/react-native#58565 · 1 comentario · 2 reacciones ·
-
Needs: Author Feedback Needs: Repro
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
react/react-native#58555 · 5 comentarios · 2 reacciones ·
Todos los issues de react/react-native
Issues similares
-
[CI] Nightly Clang build broken: structured binding captured in a lambda in FileDataSource.cpp Abiertobug build
Dificultad 1/5 Menos de una hora Aptitud para principiantes 91/100
facebookincubator/velox#19194 ·
-
JIT-compiled number -> Decimal conversion silently overflows instead of raising DECIMAL_OVERFLOW Abiertofuzz
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
ClickHouse/ClickHouse#122114 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
-
module/agent platform/macos type/bug/regression
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
-
enhancement PyCDE
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100


