Skill: Hunt Native Memory Leaks¶
Find native memory leaks using Xcode Leaks and Android Studio Memory Profiler.
Quick Command¶
# iOS: Profile with Leaks instrument
# Xcode → Product → Profile (Cmd+I) → Leaks template
# Android: Memory Profiler
# Android Studio → Run → Profile → Track Memory Consumption
When to Use¶
- App memory grows despite JS profiler showing no leaks
- Native modules suspected of leaking
- Activity recreation causes memory growth (Android)
- C++/Swift/Kotlin code under investigation
iOS: Xcode Leaks¶
Quick Check: Memory Report¶
- Run app via Xcode
- Open Debug Navigator (side panel)
- Click Memory
- Watch graph for continuous growth
Deep Analysis: Instruments Leaks¶

- Xcode → Product → Profile (or Cmd+I)
- Select Leaks template (highlighted with orange triangle icon in the grid)
- Click Choose
- Click Record (red circle)
- Use the app, perform suspect actions
- Stop recording
The template picker shows all available Instruments: - Leaks: Memory leak detection (what we need) - Allocations: All memory allocations over time - Time Profiler: CPU usage profiling - Zombies: Detect messages to deallocated objects
Analyzing Results¶
Red markers = Leaked memory detected
Click on leak to see: - Leaked Object: Type and size - Responsible Library: Which code leaked - Responsible Frame: Exact function - Stack Trace: Full call path (right panel)
Double-click function to see source code.
Common iOS Leak: Missing delete¶
// BAD: Memory leak
void createNewStrings() {
std::string* str = new std::string("Hello");
// Forgot delete str;
}
// GOOD: Fixed
void createNewStrings() {
std::string* str = new std::string("Hello");
// ... use str ...
delete str;
}
// BETTER: Use smart pointers
void createNewStrings() {
auto str = std::make_unique<std::string>("Hello");
// Automatically deleted
}
Android: Memory Profiler¶
Launch Profiler¶
- Run → Profile (or click Profile in toolbar)
- Or: View → Tool Windows → Profiler
- Select "Track Memory Consumption"
Recording¶
- Start the app
- Perform actions that might leak
- Watch memory graph for growth patterns
Analyzing Allocations¶
Memory profiler shows: - Allocations count: Objects created - Deallocations count: Objects freed - Live objects: Still in memory
If allocations >> deallocations, you have a leak.
Common Android Leak: Listener Not Removed¶
// BAD: Leaks MainActivity on config change
class MainActivity : AppCompatActivity(), Callback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
EventManager.addListener(this)
// Never removed!
}
}
// GOOD: Remove listener
class MainActivity : AppCompatActivity(), Callback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
EventManager.addListener(this)
}
override fun onDestroy() {
EventManager.removeListener(this)
super.onDestroy()
}
}
Activity Recreation Test¶
Android recreates activities on: - Screen rotation - Dark mode change - Locale change
Test: Rotate device multiple times, check if old activities are freed.
React Native note: RN opts out via android:configChanges in manifest, but native code might not.
Debugging Workflow¶
iOS¶
- Profile with Instruments Leaks
- Trigger suspect actions repeatedly
- Wait for red leak markers
- Click to identify responsible frame
- Fix and re-test
Android¶
- Profile memory consumption
- Trigger suspect actions (rotate, navigate)
- Check allocation/deallocation counts
- Look for classes with 0 deallocations
- Fix and re-test
Code Fixes by Pattern¶
Reference Cycle (Swift)¶
// BAD
class Parent {
var child: Child?
}
class Child {
var parent: Parent? // Strong reference cycle
}
// GOOD
class Parent {
var child: Child?
}
class Child {
weak var parent: Parent? // Weak breaks cycle
}
Missing Cleanup (C++)¶
// BAD
void process() {
auto* data = new LargeData();
if (error) return; // Leak!
delete data;
}
// GOOD: RAII with unique_ptr
void process() {
auto data = std::make_unique<LargeData>();
if (error) return; // Automatically cleaned up
}
Global Singleton Holding References (Kotlin)¶
// BAD: Holds strong references
object Cache {
private val items = mutableMapOf<String, Callback>()
}
// GOOD: Use weak references
object Cache {
private val items = mutableMapOf<String, WeakReference<Callback>>()
}
Verification¶
After fixing: 1. Re-run profiler 2. Perform same actions 3. Verify: - iOS: No red leak markers - Android: Allocations ≈ Deallocations
Common Pitfalls¶
- Testing in debug mode: Some leaks only appear in release
- Not waiting for GC: Force GC before concluding no leak
- Ignoring small leaks: They add up over time
- Missing cleanup in invalidate(): Turbo Modules need proper cleanup
Related Skills¶
- native-memory-patterns.md - Understanding memory patterns
- js-memory-leaks.md - JS-side leaks
- native-threading-model.md - Module invalidation