The core infrastructure for Expo Modules architecture enabling seamless integration between React Native applications and native platform code.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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>;