Skill: Native Memory Management
Understand memory management patterns in C++, Swift, and Kotlin for React Native native modules.
Quick Reference
| Pattern | Languages | Mechanism |
|---|---|---|
| Reference Counting | Swift, Obj-C | Count refs, free at zero |
| Smart pointers | C++ | Ownership encoded in pointer type |
| Garbage Collection | Kotlin/Java, JavaScript | GC scans and frees unreachable |
| Manual | C, C++ (raw pointers) | Explicit new/delete |
Key rule: Prefer stack allocation or std::unique_ptr for single ownership; use std::shared_ptr only for real shared ownership and std::weak_ptr to break cycles. In Swift, use weak when the referenced object can disappear first; use unowned only when its lifetime is guaranteed to be at least as long.
When to Use
- Writing native modules with manual memory management
- Debugging native memory leaks
- Interfacing C++ with Swift/Kotlin
- Understanding reference counting vs garbage collection
C++ Smart Pointers
std::unique_ptr - Single Owner
#include <memory>
void takeOwnership(std::unique_ptr<std::string> s) {
std::cout << *s;
// Automatically deleted when function ends
}
int main() {
auto str = std::make_unique<std::string>("Hello");
// Can only be moved, not copied
takeOwnership(std::move(str));
// str is now empty
return 0;
}std::shared_ptr - Multiple Owners
void useShared(std::shared_ptr<std::string> s) {
std::cout << *s; // Reference count temporarily +1
}
void useReference(const std::shared_ptr<std::string>& s) {
std::cout << *s; // No ref count change (passed by reference)
}
int main() {
auto str = std::make_shared<std::string>("Hello");
useShared(str); // Copies pointer, ref count +1
useReference(str); // No copy, ref count unchanged
std::cout << *str; // Still valid
return 0;
}std::weak_ptr - Non-Owning Reference
void useWeak(std::weak_ptr<std::string> weak) {
if (auto shared = weak.lock()) { // Check if still exists
std::cout << *shared;
} else {
std::cout << "Object destroyed";
}
}
int main() {
auto str = std::make_shared<std::string>("Hello");
std::weak_ptr<std::string> weak = str; // No ref count increase
useWeak(weak); // Works
str.reset(); // Destroys object
useWeak(weak); // "Object destroyed"
return 0;
}Swift ARC (Automatic Reference Counting)
class Person {
let name: String
init(name: String) { self.name = name }
deinit { print("Deallocated") }
}
do {
let person1 = Person(name: "John") // Ref count: 1
do {
let person2 = person1 // Ref count: 2
} // person2 out of scope, ref count: 1
} // person1 out of scope, ref count: 0, "Deallocated"Breaking Reference Cycles with weak
// BAD: Reference cycle (memory leak)
class A {
var b: B?
}
class B {
var a: A? // Strong reference creates cycle
}
// GOOD: Use weak to break cycle
class A {
var b: B?
}
class B {
weak var a: A? // Weak reference, doesn't prevent deallocation
}Kotlin/Android GC
Weak References for Caches
WeakHashMap weakens keys, not values. Store WeakReference values explicitly when the cached value itself should not be strongly retained. Do not rely on deterministic System.gc() behavior in tests.
WeakReference for Callbacks
class DataManager {
// Weak references to listeners prevent memory leaks
private val listeners = mutableListOf<WeakReference<DataListener>>()
fun addListener(listener: DataListener) {
listeners.add(WeakReference(listener))
}
fun notifyListeners(data: String) {
listeners.forEach { ref ->
ref.get()?.onDataChanged(data)
}
}
}Common Memory Leak Sources
1. Forgetting to Delete (C++)
// BAD: Memory leak
int main() {
std::string* str = new std::string("Hello");
// Forgot to delete!
return 0;
}
// GOOD: Use smart pointers or stack allocation
int main() {
auto str = std::make_unique<std::string>("Hello");
// Automatically deleted
return 0;
}2. Reference Cycles (Swift/C++)
// BAD: Cycle
class A { std::shared_ptr<B> b; };
class B { std::shared_ptr<A> a; };
// GOOD: Break with weak_ptr
class A { std::shared_ptr<B> b; };
class B { std::weak_ptr<A> a; };3. Unremoved Listeners (Kotlin)
// BAD: Listener never removed
class MyClass {
private val listener = object : Callback {
override fun onEvent() { /* ... */ }
}
init {
EventManager.addListener(listener)
// Never removed!
}
}
// GOOD: Implement cleanup
class MyClass : AutoCloseable {
private val listener = object : Callback {
override fun onEvent() { /* ... */ }
}
init {
EventManager.addListener(listener)
}
override fun close() {
EventManager.removeListener(listener)
}
}Swift Unmanaged (Advanced)
Use Unmanaged only for C interop that explicitly transfers ownership. Match passRetained with takeRetainedValue, and passUnretained with takeUnretainedValue.
Best Practices Summary
| Language | Best Practice |
|---|---|
| C++ | Prefer stack or unique_ptr; use shared_ptr only for shared ownership |
| Swift | Use weak for delegates and disappearing references; use unowned only with guaranteed lifetime |
| Kotlin | Implement AutoCloseable, use WeakReference |
| All | Prefer stack over heap when possible |
Related Skills
- native-memory-leaks.md - Find leaks with profilers
- native-turbo-modules.md - Build memory-safe modules