REACT NATIVE PERFORMANS VE BRIDGE

Veri Akademi7 dk okuma
React Native bridge mimarisi: native tarafı ile JavaScript dünyasını bağlayan köprü metaforu

Uygulama TestFlight'a çıktı, ürün ekibi mutlu. Ama kullanıcı geri bildirimleri damlamaya başladı: "ürün listesini kaydırırken takılıyor", "klavye açılırken ekran donuyor", "kamera ekranından çıkış yarım saniye sürüyor". Profiler açılıyor, JS thread temiz görünüyor, native taraf da boş. Suçlu görünmez bir yerde: iki dünyayı birbirine bağlayan köprüde. React Native'in klasik bridge mimarisi, JSON serileştirme ve asenkron mesajlaşma üzerine kuruluydu — küçük uygulamalarda fark edilmeyen bu maliyet, büyük listelerle ve sık etkileşimle birlikte gözle görülür jank'a dönüşüyor.

1. Klasik Bridge Neden Yavaşlar?

React Native'in ilk mimarisinde JavaScript thread, native (UI ve modules) thread'lerle doğrudan konuşamıyordu. Her çağrı şu yolu izliyordu: JS tarafında nesne hazırlanır, JSON'a serileştirilir, bridge üzerinden asenkron olarak gönderilir, native tarafta deserialize edilir ve çalıştırılır. Cevap aynı yolu tersine kat ediyor.

Bu modelin üç temel maliyeti vardı:

  • Serileştirme: Her mesaj JSON string'ine dönüştürülüyor; büyük listeler veya sık güncellenen layout değerleri CPU yiyor.
  • Asenkron iletişim: Senkron sonuç gereken durumlarda (ölçüm, layout) ek frame gecikmesi oluşuyor.
  • Tek kuyruk: Mesajlar sıraya giriyor; ağır bir batch arkasında bekleyen küçük UI güncellemesi geç ulaşıyor.

Sonuç: 16 ms'lik frame bütçesi kolayca aşılıyor, scroll'da boş kareler oluşuyor. Konuya ilişkin konunun teknik kaynakları ek bir başvuru kaynağı olarak değerlendirilebilir.

2. FlatList Jank'inin Anatomisi

FlatList scroll jank anatomisi: kaydırılan liste kartlarında frame düşmesi ve görünüm pencereleri

Performans şikayetlerinin büyük çoğunluğu FlatList ekranlarından gelir. Listede her satır bir View, içinde Image, Text, belki bir TouchableOpacity. Kullanıcı parmağını sürüklediğinde JS thread yeni satırların layout'unu hesaplar, props'ları bridge üzerinden iletir. Hızlı scroll'da bu hesap yetişemez ve liste boş alanlarla, sonra ani sıçramalarla doldurulur.

Sık görülen sebepler:

  1. Anonim fonksiyonlarla renderItem tanımlamak — her render'da yeni referans, satırlar gereksiz yere yeniden çiziliyor.
  2. keyExtractor'ı index'e bağlamak — sıralama değişince tüm satırlar yeniden mount oluyor.
  3. Satır içinde inline style nesnesi — sığ karşılaştırma her seferinde false dönüyor.
  4. Yüksek çözünürlüklü görsel yüklerken cache stratejisi olmaması.
  5. getItemLayout tanımlanmamış olması — kütüphane her satırın yüksekliğini ölçmek için fazladan iş yapıyor.

Bu ayarları düzeltmek bridge yükünü azaltır, ama mimari sınırı kaldırmaz. Konuyu uçtan uca ele almak için React Native eğitimi içeriğinden yararlanabilirsiniz.

3. JSI: Bridge'in Yerini Alan Doğrudan Köprü

JSI (JavaScript Interface), C++ tarafında tanımlı hafif bir arayüzdür. JS motoru (Hermes, JSC veya V8) ile native dünya arasındaki iletişimi JSON serileştirmesi olmadan kurar. JS tarafından çağrılan bir fonksiyon, C++ tarafındaki host object üzerinden doğrudan native koda iner.

Pratik kazanımlar:

  • Senkron çağrılar mümkün — ölçüm, layout veya animasyon değerleri için frame kaybı yok.
  • JSON marshalling kalktığı için CPU maliyeti düşüyor.
  • Büyük veri (ArrayBuffer, native nesne referansı) sıfır kopya ile geçirilebiliyor.

JSI'nin etkisini en çok hisseden alanlar: Reanimated 2/3 ile worklet'lerin UI thread'inde çalışması, MMKV gibi senkron depolama kütüphaneleri, vision-camera gibi yüksek frekanslı frame işleyicileri.

4. Fabric: Yeni Render Pipeline

Eski Bridge mimarisi ile yeni JSI doğrudan bağlantısının yan yana karşılaştırma şeması

Fabric, eski UIManager'ı modernleştirir. Eski mimaride shadow tree (gölge ağaç) Java/ObjC tarafında inşa ediliyordu; Fabric ile bu yapı C++ tarafına taşındı ve her platformda aynı kod paylaşılıyor.

Performans açısından üç önemli değişiklik:

  • Senkron layout: JS tarafı, ölçüm sonuçlarını bekleyebiliyor; özellikle metin tabanlı dinamik içerik için frame kaymaları azalıyor.
  • Concurrent rendering uyumu: React 18'in Suspense ve transition'ları, Fabric ile birlikte doğru çalışıyor; ağır güncellemeler arka planda hazırlanıp tek seferde yansıtılıyor.
  • Immutable shadow tree: Çift tampon prensibiyle, eski ağaç ekrandayken yenisi inşa ediliyor; çakışma kaynaklı titremeler kayboluyor.

5. TurboModules ve Yeni Native Modüller

Klasik native modüllerde tüm modüller uygulama açılışında yükleniyordu — başlangıç süresi şişiyor, bellek boşa harcanıyordu. TurboModules, JSI üzerinde tembel yükleme (lazy loading) sağlar: modül ilk çağrıldığında bağlanır. Codegen ile tip güvenli arayüzler üretilir, JS ve native taraf arasındaki uyumsuzluklar derleme zamanında yakalanır.

6. Yeni Mimariye Geçişte Pratik Yol Haritası

Mevcut bir uygulamayı yeni mimariye taşırken adım adım ilerlemek riski düşürür:

  1. Hermes'i etkinleştirin; JSI'den tam yararlanabilmek için motor seçimi belirleyicidir.
  2. Profil çıkarın — Flipper veya Perfetto ile mevcut darboğazları ölçün, körlemesine geçiş yapmayın.
  3. Kullandığınız üçüncü parti kütüphanelerin yeni mimariyle uyumlu sürümlerini kontrol edin; uyumsuz tek bir kütüphane interop katmanını tetikleyip kazancı eritebilir.
  4. Reanimated, Gesture Handler, Screens gibi temel paketleri önce güncelleyin.
  5. newArchEnabled bayrağını dev build'de açın, üç-dört sprint boyunca regresyon takibi yapın.
  6. Production'a önce düşük riskli ekranlarla, feature flag arkasında çıkın.

Detaylı uygulama örnekleri ve modül yazımı için React Native eğitim içeriğini inceleyebilirsiniz.

7. Ölçmeden İyileştirme Yok

Bridge'in kalktığını söylemek tek başına yetmez — kullanıcı cihazında frame süreleri düşmediyse mimari değişikliği marketing slaytında kalır. Android tarafında Systrace ve Perfetto, iOS tarafında Instruments Time Profiler, her ikisinde de Flipper'ın Hermes profiler eklentisi gerçek veriyi verir. Hedef basit: kullanıcı parmağını sürüklerken 60 FPS'lik (veya cihaz destekliyorsa 120 FPS'lik) bütçenin altında kalan kareler.

Yeni mimari sihirli değnek değil; klasik bridge'in yapısal sınırlarını kaldıran bir altyapı. Liste optimizasyonu, görsel cache, gereksiz re-render temizliği hâlâ geçerli. Ama bu temel hijyen tamamken JSI ve Fabric'e geçmek, daha önce ulaşılamayan akıcılığı erişilebilir kılar.

Sıkça Sorulan Sorular

Klasik bridge ile JSI arasındaki temel fark nedir?

Klasik bridge, JS ve native taraf arasındaki her mesajı JSON'a serileştirip asenkron bir kuyruğa atar. JSI ise C++ tarafında tanımlı bir arayüzdür; JS motoru native fonksiyonlara host object üzerinden doğrudan, senkron olarak ulaşır. Serileştirme maliyeti ve asenkron gecikme ortadan kalkar, büyük veri sıfır kopya ile geçirilebilir.

FlatList'te scroll jank'i için yeni mimariye geçmeden önce ne yapılmalı?

Önce temel hijyen tamamlanmalı: renderItem ve keyExtractor'ı stabil referanslarla tanımlamak, getItemLayout sağlamak, satır bileşenlerini memoize etmek, inline style nesnelerinden kaçınmak ve görselleri uygun çözünürlükte cache'lemek. Bu adımlar Fabric/JSI olmadan da kazandırır; mimari geçişin etkisini de daha net ölçmenizi sağlar.

Fabric'e geçtim ama performans değişmedi, neden?

İki yaygın sebep var. Birincisi, uygulamanız zaten temiz bir liste/render mimarisine sahipti ve bridge darboğazı asıl sorun değildi; profilleme bunu önceden gösterirdi. İkincisi, kullandığınız üçüncü parti kütüphaneler eski mimaride çalışıyor ve interop katmanı devreye giriyor; bu katman kazancı eritir. Bağımlılıkları yeni mimari uyumlu sürümlere çekmek gerekir.

Hermes etkinleştirmek tek başına yeterli mi?

Hermes başlangıç süresini ve bellek tüketimini iyileştirir, JSI tabanlı kütüphaneler için altyapı sağlar. Ancak bridge mimarisini değiştirmez. Reanimated worklet'leri veya MMKV gibi JSI kütüphanelerinin tam performansını almak için Hermes gerekli ama yeterli değil; new architecture bayrağı ve uyumlu paket sürümleri de tamamlayıcıdır.

TurboModules ile klasik native modüller arasında ne fark var?

Klasik native modüller uygulama açılışında topluca yükleniyor, bu da cold start süresini uzatıyordu. TurboModules, JSI üzerinde tembel yüklemeyi mümkün kılar: modül ilk çağrıldığında bağlanır. Codegen ile tip güvenli arayüzler üretildiğinden JS ve native taraf arasındaki uyumsuzluklar derleme zamanında yakalanır, runtime hataları azalır.

Yeni mimariye geçişte hangi araçlarla ölçüm yapmalıyım?

Android tarafında Systrace ve Perfetto, iOS tarafında Instruments'ın Time Profiler ve Core Animation şablonları, her iki platformda da Flipper'ın Hermes profiler eklentisi temel araçlardır. Frame süreleri, JS thread bloklanma süresi ve native UI thread iş yükü birlikte izlenmeli; tek bir metriğe bakmak yanıltıcı sonuç verir.

Reanimated worklet'leri neden JSI olmadan aynı performansı vermez?

Worklet mantığı animasyon değerlerini UI thread üzerinde, JS bridge'i beklemeden hesaplamaya dayanır. Bunun için JS fonksiyonunun runtime'ı UI thread'e taşınması gerekir; bu da JSI'nin sağladığı senkron, serileştirmesiz native-JS köprüsü olmadan mümkün değildir. Klasik bridge ile aynı animasyon her frame'de JSON marshalling yapar ve düşer.

Eski uygulamamı yeni mimariye geçirmeden önce hangi sinyalleri aramalıyım?

Profil çıktısında JS thread'in sık bloklanması, bridge mesaj sayısının yüksek olması, FlatList ekranlarında frame drop'ları, Reanimated kullanılan animasyonlarda jank, ölçüm gerektiren layout işlemlerinde gecikme — bunlar mimari geçişin somut kazanç vereceği sinyallerdir. Sadece moda gereği geçmek, regresyon riskini kazançtan büyük yapar.