Efficient memory sharing between JavaScript and native code with manual memory management capabilities for performance-critical scenarios.
Base class for objects that share memory between JavaScript and native code with manual lifecycle management.
/**
* Base class for all shared objects that extends EventEmitter
* Implementation is written in C++ and installed through JSI
* Provides manual memory management for performance-critical scenarios
*/
class SharedObject<TEventsMap extends EventsMap = Record<never, never>>
extends EventEmitter<TEventsMap>
implements EventEmitter<TEventsMap> {
/**
* Detaches JS and native objects to allow native deallocation
* before JS garbage collection. Subsequent native function calls will throw.
* Use only when manual memory management is needed for performance.
* React hooks usually handle this automatically (e.g., useVideoPlayer, useImage).
*/
release(): void;
}Usage Examples:
import { SharedObject } from "expo-modules-core";
// Example: Image processing shared object
type ImageEvents = {
processingComplete: (result: ProcessingResult) => void;
error: (error: Error) => void;
};
class ImageProcessor extends SharedObject<ImageEvents> {
// Native methods would be bound here
processImage?: (imageData: ArrayBuffer) => Promise<ProcessingResult>;
}
// Manual memory management
const processor = new ImageProcessor();
// Process image
processor.addListener("processingComplete", (result) => {
console.log("Processing complete:", result);
// Important: Release when done to free native memory immediately
processor.release();
});
try {
await processor.processImage?.(imageData);
} catch (error) {
console.error("Processing failed:", error);
processor.release(); // Always release on error too
}Holds references to native objects, enabling efficient passing of native instances between different modules.
/**
* A SharedObject that holds a reference to any native object
* Allows passing references to native instances among independent libraries
* without additional file system reads/writes
*/
class SharedRef<
TNativeRefType extends string = 'unknown',
TEventsMap extends EventsMap = Record<never, never>
> extends SharedObject<TEventsMap>
implements SharedObject<TEventsMap> {
/**
* The type of the native reference (e.g., 'Drawable', 'UIImage')
* Used for type checking when passing between modules
*/
nativeRefType: string;
}Usage Examples:
import { SharedRef } from "expo-modules-core";
// Example: Image reference that can be shared between modules
type ImageRef = SharedRef<'UIImage' | 'Drawable', {
loaded: () => void;
error: (error: Error) => void;
}>;
// Create image reference (typically done by expo-image)
declare function createImageRef(uri: string): ImageRef;
// Use in image manipulation (typically done by expo-image-manipulator)
declare function manipulateImage(imageRef: ImageRef, operations: Operation[]): Promise<ImageRef>;
// Use in image component (typically done by expo-image)
declare function displayImage(imageRef: ImageRef): void;
// Example usage
const originalImage = createImageRef("https://example.com/image.jpg");
// Listen for load event
originalImage.addListener("loaded", () => {
console.log("Image loaded");
});
// Pass reference between modules efficiently (no file I/O)
const manipulatedImage = await manipulateImage(originalImage, [
{ resize: { width: 300, height: 200 } },
{ flip: { vertical: true } }
]);
// Display the manipulated image
displayImage(manipulatedImage);
// Check reference type
console.log("Native type:", manipulatedImage.nativeRefType); // 'UIImage' on iOS, 'Drawable' on Android
// Clean up when done
originalImage.release();
manipulatedImage.release();React hook that automatically manages SharedObject lifecycle with component unmounting and dependency changes.
/**
* React hook that returns a shared object with automatic cleanup
* Object is automatically released when component unmounts or dependencies change
* @param factory - Function that creates the shared object
* @param dependencies - Dependency array like useEffect
* @returns The shared object instance
*/
function useReleasingSharedObject<TSharedObject extends SharedObject>(
factory: () => TSharedObject,
dependencies: DependencyList
): TSharedObject;Usage Examples:
import { useReleasingSharedObject } from "expo-modules-core";
import { useEffect, useState } from "react";
// Example: Audio player component
function AudioPlayer({ audioUri }: { audioUri: string }) {
// SharedObject is automatically managed
const audioPlayer = useReleasingSharedObject(
() => createAudioPlayer(audioUri), // Factory function
[audioUri] // Dependencies - player recreated when URI changes
);
const [isPlaying, setIsPlaying] = useState(false);
useEffect(() => {
// Listen for playback events
const subscription = audioPlayer.addListener("playbackStatusChanged", (status) => {
setIsPlaying(status.isPlaying);
});
return () => subscription.remove();
}, [audioPlayer]);
const togglePlayback = async () => {
if (isPlaying) {
await audioPlayer.pauseAsync();
} else {
await audioPlayer.playAsync();
}
};
// No need to manually call release() - hook handles it automatically
return (
<button onClick={togglePlayback}>
{isPlaying ? "Pause" : "Play"}
</button>
);
}
// Example: Image processor with dependency changes
function ImageProcessor({ imageUri, filters }: { imageUri: string; filters: Filter[] }) {
const processor = useReleasingSharedObject(
() => createImageProcessor(imageUri, filters),
[imageUri, filters] // Processor recreated when image or filters change
);
const [processedImage, setProcessedImage] = useState<string | null>(null);
useEffect(() => {
processor.processAsync().then(setProcessedImage);
}, [processor]);
return processedImage ? <img src={processedImage} /> : <div>Processing...</div>;
}Guidelines for efficient memory management with shared objects:
import { SharedObject, useReleasingSharedObject } from "expo-modules-core";
// ✅ Good: Use React hook for automatic management
function GoodComponent({ uri }: { uri: string }) {
const sharedObject = useReleasingSharedObject(
() => createSharedObject(uri),
[uri]
);
// Automatically released when component unmounts or uri changes
return <div>Using shared object</div>;
}
// ⚠️ Manual management - only when necessary
function ManualComponent({ uri }: { uri: string }) {
const [sharedObject, setSharedObject] = useState<SharedObject | null>(null);
useEffect(() => {
const obj = createSharedObject(uri);
setSharedObject(obj);
return () => {
// ✅ Always release in cleanup
obj.release();
};
}, [uri]);
return <div>Using shared object</div>;
}
// ❌ Bad: Forgetting to release
function BadComponent({ uri }: { uri: string }) {
const sharedObject = createSharedObject(uri);
// Memory leak - object never released
return <div>Using shared object</div>;
}
// ✅ Good: Error handling with release
async function processWithSharedObject(data: ArrayBuffer) {
const processor = createSharedProcessor();
try {
const result = await processor.process(data);
return result;
} catch (error) {
console.error("Processing failed:", error);
throw error;
} finally {
// ✅ Always release in finally block
processor.release();
}
}/**
* React dependency list type for useReleasingSharedObject
*/
type DependencyList = ReadonlyArray<any>;
/**
* Base type for events map used by SharedObject
*/
type EventsMap = Record<string, (...args: any[]) => void>;