This isn't React Native-specific knowledge dressed up as general programming. The core problem is universal: what happens when two memory management systems with fundamentally different rules need to cooperate on the same piece of memory?
JavaScript's Hermes GC: non-deterministic, safe, never frees live objects.
C++ shared_ptr: deterministic, immediate, trusts the programmer completely.
When you create a JSI HostObject (a C++ class that JavaScript holds as a regular object), both systems hold references. The GC holds a shared_ptr copy inside the JS object's native backing. Your C++ code might hold others. The contract: the native object lives until the last reference from either world drops.
Part 5 covers the object model — three virtual methods (get, set, getPropertyNames) that turn a C++ class into something JavaScript can hold, inspect, and call methods on. Destructor runs when GC collects. RAII gives you automatic native resource cleanup.
Part 6 covers the hard part:
- Dangling this: JS can detach a method from the object (const fn = obj.increment), drop the object, then call fn() — use-after-free via a captured raw pointer. Fix: weak_from_this().
- Zero-copy binary data: ArrayBuffer lets both sides read the same bytes. Subclass jsi::MutableBuffer, hand it to the runtime via shared_ptr. No serialization, no JSON, no copies.
- Three bug categories that cover every JSI memory issue: use-after-free (stashing pointers past sync boundary), circular shared_ptr leaks, and raw allocation leaks.
The general takeaway: when bridging GC and manual memory, shared_ptr is the universal adapter, weak_ptr breaks cycles and prevents prevented cleanup, and raw pointers are never safe to store across async boundaries.
Part 5: https://heartit.tech/react-native-jsi-deep-dive-part-5-hostobjects-exposing-c-classes-to-javascript/