[iOS] Image re-rendering with potential memory leak
Description
I am trying to build a pdf document viewer using react native skia and reanimated. This requires rendering bitmap tiles of a pdf page at different resolution levels when we peform pinch zoom on canvas. As a result of that, I have to regenerat the Skia Image multiple times and assign to the same image component. Please have a look at following simplified code sample. The issue I am struggling is, when I zoom in and generate images of 3000 x 3000 pixels, memory usage goes up as expected but when I zoom out and generate 300x300 bitmaps, memory does not go back down. I initially though that this is related to delayed garbage collection but it seems like not the case as the memory is occupied permanantly for a long period of time. I lookd at the memory profile and there is a lot of objects with stuck at SkData::MakeWithCopy and I think those are the images I created on demad when the pinch zoom ended. Can you help me to figure out why this memory leak is happening and what could be the potential resolution for this?
import React, { useEffect, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Canvas, Skia, AlphaType, ColorType, Fill, Image, SkImage } from '@shopify/react-native-skia';
import { GestureDetector, Gesture, GestureHandlerRootView } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useDerivedValue,
runOnJS,
} from 'react-native-reanimated';
function App(): React.JSX.Element {
const tileSize = 512;
const scale = useSharedValue(1);
const translationX = useSharedValue(0);
const translationY = useSharedValue(0);
const [scaleEnd, setScaleEnd] = useState(1);
const [skiaImage, setSkiaImage] = useState<SkImage>();
const createTile = (tileSize: number) => {
const pixels = new Uint8Array(tileSize * tileSize * 4);
pixels.fill(255);
let i = 0;
for (let x = 0; x < tileSize; x++) {
for (let y = 0; y < tileSize; y++) {
pixels[i++] = (x * y) % 255;
}
}
const data = Skia.Data.fromBytes(pixels);
const img = Skia.Image.MakeImage(
{
width: tileSize,
height: tileSize,
alphaType: AlphaType.Opaque,
colorType: ColorType.RGBA_8888,
},
data,
tileSize * 4
);
return img;
}
if (skiaImage === null) {
return <View style={styles.container} />;
}
const panGesture = Gesture.Pan().onChange((e) => {
translationX.value += e.changeX;
translationY.value += e.changeY;
});
const scaleGesture = Gesture.Pinch().onChange((e) => {
scale.value += e.scaleChange - 1;
}).onEnd(() => {
runOnJS(() => {
setScaleEnd(scale.value);
})();
}).runOnJS(true);
useEffect(() => {
const skImg = createTile(tileSize * scale.value);
console.log("Skia image calculating for tile size " + tileSize * scale.value);
if (skImg !== null) {
setSkiaImage(skImg);
}
}, [scaleEnd]);
return (
<GestureHandlerRootView>
<View style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }}>
<Fill color="pink" />
<Image image={skiaImage} x={translationX} y={translationY} width={useDerivedValue(() => tileSize * scale.value)} height={useDerivedValue(() => tileSize * scale.value)} />
</Canvas>
<GestureDetector gesture={Gesture.Race(panGesture, scaleGesture)}>
<Animated.View style={StyleSheet.absoluteFill} />
</GestureDetector>
</View>
</GestureHandlerRootView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "pink",
},
canvasContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
canvas: {
flex: 1,
},
animatedCanvas: {
flex: 1,
},
});
export default App;
React Native Skia Version
1.9.0
React Native Version
0.76.5
Using New Architecture
- [x] Enabled
Steps to Reproduce
Run the attached code and try multiple pinch zoom on iOS
Snack, Code Example, Screenshot, or Link to Repository
Currently we are not using setExternalMemory pressure from Hermes and therefore this skiaImage gets garbage collected eventually but at a much later time than expected. calling skiaImage.dispose() when setting a new image should help. I would be curious to see the behavior you are experiencing. In my case we can log that all objects are properly deleted from the garbage collector but I still see a very high memory pressure, not sure why. I asked the questions on the hermes github: https://github.com/facebook/hermes/issues/1518
On Thu, Jan 23, 2025 at 3:22 PM Dimuthu Wannipurage @.***> wrote:
Description
I am trying to build a pdf document viewer using react native skia and reanimated. This requires rendering bitmap tiles of a pdf page at different resolution levels when we peform pinch zoom on canvas. As a result of that, I have to regenerat the Skia Image multiple times and assign to the same image component. Please have a look at following simplified code sample. The issue I am struggling is, when I zoom in and generate images of 3000 x 3000 pixels, memory usage goes up as expected but when I zoom out and generate 300x300 bitmaps, memory does not go back down. I initially though that this is related to delayed garbage collection but it seems like not the case as the memory is occupied permanantly for a long period of time. I lookd at the memory profile and there is a lot of objects with stuck at SkData::MakeWithCopy and I think those are the images I created on demad when the pinch zoom ended. Can you help me to figure out why this memory leak is happening and what could be the potential resolution for this?
import React, { useEffect, useState } from 'react'; import { View, StyleSheet } from 'react-native'; import { Canvas, Skia, AlphaType, ColorType, Fill, Image, SkImage } from @.***/react-native-skia'; import { GestureDetector, Gesture, GestureHandlerRootView } from 'react-native-gesture-handler'; import Animated, { useSharedValue, useDerivedValue, runOnJS, } from 'react-native-reanimated';
function App(): React.JSX.Element {
const tileSize = 512; const scale = useSharedValue(1); const translationX = useSharedValue(0); const translationY = useSharedValue(0); const [scaleEnd, setScaleEnd] = useState(1); const [skiaImage, setSkiaImage] = useState<SkImage>();
const createTile = (tileSize: number) => { const pixels = new Uint8Array(tileSize * tileSize * 4); pixels.fill(255); let i = 0; for (let x = 0; x < tileSize; x++) { for (let y = 0; y < tileSize; y++) { pixels[i++] = (x * y) % 255; } } const data = Skia.Data.fromBytes(pixels); const img = Skia.Image.MakeImage( { width: tileSize, height: tileSize, alphaType: AlphaType.Opaque, colorType: ColorType.RGBA_8888, }, data, tileSize * 4 );
return img; }if (skiaImage === null) { return <View style={styles.container} />; }
const panGesture = Gesture.Pan().onChange((e) => { translationX.value += e.changeX; translationY.value += e.changeY; });
const scaleGesture = Gesture.Pinch().onChange((e) => { scale.value += e.scaleChange - 1; }).onEnd(() => { runOnJS(() => { setScaleEnd(scale.value); })(); }).runOnJS(true);
useEffect(() => { const skImg = createTile(tileSize * scale.value); console.log("Skia image calculating for tile size " + tileSize * scale.value); if (skImg !== null) { setSkiaImage(skImg); } }, [scaleEnd]);
return ( <GestureHandlerRootView> <View style={{ flex: 1 }}> <Canvas style={{ flex: 1 }}> <Fill color="pink" /> <Image image={skiaImage} x={translationX} y={translationY} width={useDerivedValue(() => tileSize * scale.value)} height={useDerivedValue(() => tileSize * scale.value)} /> </Canvas> <GestureDetector gesture={Gesture.Race(panGesture, scaleGesture)}> <Animated.View style={StyleSheet.absoluteFill} /> </GestureDetector> </View> </GestureHandlerRootView> ); };
const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "pink", }, canvasContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, canvas: { flex: 1, }, animatedCanvas: { flex: 1, }, });
export default App;
React Native Skia Version
1.9.0
React Native Version
0.76.5
Using New Architecture
Enabled
Steps to Reproduce
Run the attached code and try multiple pinch zoom on iOS
Snack, Code Example, Screenshot, or Link to Repository
Screenshot.2025-01-23.at.9.04.32.AM.png (view on web) Screenshot.2025-01-23.at.9.18.32.AM.png (view on web)
— Reply to this email directly, view it on GitHub or unsubscribe. You are receiving this email because you are subscribed to this thread.
Triage notifications on the go with GitHub Mobile for iOS or Android.
@wcandillon I tried to dispose the old image but that raised some EXEC_BAD_ACCESS errors on C++. Please let me whether following way is correct?
useEffect(() => {
const skImg = createTile(tileSize * scale.value);
console.log("Skia image calculating for tile size " + tileSize * scale.value);
if (skImg !== null) {
const oldSkiaImage = skiaImage;
setSkiaImage(skImg);
if (oldSkiaImage) {
oldSkiaImage.dispose();
}
}
}, [scaleEnd]);
Had a similiar issue only on iOS when re-rendering the skia image from a base64 png it would cause a crash. Can also provide some logs if it helps.
yes any log/reproduction would be extremely helpful Ronald, did get a user report that hinted at a similar problem.
On Wed, Feb 5, 2025 at 10:56 PM Ronald Goedeke @.***> wrote:
Had a similiar issue only on iOS when re-rendering the skia image from a base64 png it would cause a crash. Can also provide some logs if it helps.
— Reply to this email directly, view it on GitHub or unsubscribe. You are receiving this email because you commented on the thread.
Triage notifications on the go with GitHub Mobile for iOS or Android.
@wcandillon
Skia version: "@shopify/react-native-skia": "^1.11.4" RN version: "react-native": "0.76.6"
mobilizeV2(57863,0x16c2bf000) malloc: *** error for object 0xc: pointer being freed was not allocated
mobilizeV2(57863,0x16c2bf000) malloc: *** set a breakpoint in malloc_error_break to debug
Issue occurs when creating an image like this:
const skiaImage = Skia.Image.MakeImageFromEncoded(
Skia.Data.fromBase64(
"base64 string is in file below"
)
);
Base64 string of the image image.txt
@ronickg thanks a lot, this error seems consistent with some other reports we've seen. Just to make sure, it's not as simple as running the snippet you kindly sent to reproduce the error right? I have to assume that it used in another runtime context (reanimated worklet)? Let me know if you have a strong lead on how to reproduce the crash.
@wcandillon It should be as simple as just running the code without any worklets. To reproduce the error I just created a screen using expo router and just navigated to it. When wrapping it in a memo without any dependencies it works fine as it doesn't re-render, as the issue happens as soon as it needs to run the code a second time. And only had this issue on iOS, on android it works.
I'll make a new project repo to mimic the error which should make debugging easier.
Thanks a lot 🫶🏻
Okay, i wasn't able to reproduce the issue in a new repo, so I went back to testing my initial one. And initially I though it was crashing even with just the simple code snippet above, but I think i may have not correctly copied the base64 string. So from all that yes I do think its due to the worklets. I am using the latest react native worklets core and set the base64 string like so:
const saveImagesAndStop = Worklets.createRunOnJS(
async (imagesString: string) => {
setSnapShotBase64(imagesString);
}
);
This function is called from inside the frameProcessor in vision camera. Which I am sure is somehow causing the crash.
So I don't think there is anything wrong on the skia side. Maybe this helps someone else who has the same issue. Thx
@DImuthuUpe any chance you could update your example at the top of the issue to use .dipose (with the crash, and then I can investigate it?)
On Thu, Feb 6, 2025 at 2:05 PM Ronald Goedeke @.***> wrote:
Okay, i wasn't able to reproduce the issue in a new repo, so I went back to testing my initial one. And initially I though it was crashing even with just the simple code snippet above, but I think i may have not correctly copied the base64 string. So from all that yes I do think its due to the worklets. I am using the latest react native worklets core and set the base64 string like so:
const saveImagesAndStop = Worklets.createRunOnJS( async (imagesString: string) => { setSnapShotBase64(imagesString); } );
This function is called from inside the frameProcessor in vision camera. Which I am sure is somehow causing the crash.
So I don't think there is anything wrong on the skia side. Maybe this helps someone else who has the same issue. Thx
— Reply to this email directly, view it on GitHub or unsubscribe. You are receiving this email because you commented on the thread.
Triage notifications on the go with GitHub Mobile for iOS or Android.
@wcandillon here it is. For some reasosn, this does not crash for me now but still the memory does not come down when I zoom out. Try to zoom in until you see memory usage goes in beyond 1 gb and try to zoom out. You might notice that the memory does not scale back to 500MBs
import React, { useEffect, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Canvas, Skia, AlphaType, ColorType, Fill, Image, SkImage } from '@shopify/react-native-skia';
import { GestureDetector, Gesture, GestureHandlerRootView } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useDerivedValue,
runOnJS,
} from 'react-native-reanimated';
function App(): React.JSX.Element {
const tileSize = 512;
const scale = useSharedValue(1);
const translationX = useSharedValue(0);
const translationY = useSharedValue(0);
const [scaleEnd, setScaleEnd] = useState(1);
const [skiaImage, setSkiaImage] = useState<SkImage>();
const createTile = (tileSize: number) => {
const pixels = new Uint8Array(tileSize * tileSize * 4);
pixels.fill(255);
let i = 0;
for (let x = 0; x < tileSize; x++) {
for (let y = 0; y < tileSize; y++) {
pixels[i++] = (x * y) % 255;
}
}
const data = Skia.Data.fromBytes(pixels);
const img = Skia.Image.MakeImage(
{
width: tileSize,
height: tileSize,
alphaType: AlphaType.Opaque,
colorType: ColorType.RGBA_8888,
},
data,
tileSize * 4
);
return img;
}
if (skiaImage === null) {
return <View style={styles.container} />;
}
const panGesture = Gesture.Pan().onChange((e) => {
translationX.value += e.changeX;
translationY.value += e.changeY;
});
const scaleGesture = Gesture.Pinch().onChange((e) => {
scale.value += e.scaleChange - 1;
}).onEnd(() => {
runOnJS(() => {
setScaleEnd(scale.value);
})();
}).runOnJS(true);
useEffect(() => {
const skImg = createTile(tileSize * scale.value);
console.log("Skia image calculating for tile size " + tileSize * scale.value);
if (skImg !== null) {
const oldSkiaImage = skiaImage;
setSkiaImage(skImg);
if (oldSkiaImage) {
oldSkiaImage.dispose();
}
}
}, [scaleEnd]);
return (
<GestureHandlerRootView>
<View style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }}>
<Fill color="pink" />
<Image image={skiaImage} x={translationX} y={translationY} width={useDerivedValue(() => tileSize * scale.value)} height={useDerivedValue(() => tileSize * scale.value)} />
</Canvas>
<GestureDetector gesture={Gesture.Race(panGesture, scaleGesture)}>
<Animated.View style={StyleSheet.absoluteFill} />
</GestureDetector>
</View>
</GestureHandlerRootView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "pink",
},
canvasContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
canvas: {
flex: 1,
},
animatedCanvas: {
flex: 1,
},
});
export default App;
The topic of the memory not coming down is one I need to be more educated on. We've ran some benchmarks where we could measure that all objects were deleted (and the gc running at regular intervals), running some drawings for hours. However the allocated memory we would see on the iOS perf monitor of the app would never come down.
I would like to learn more about Hermes behaviours there and understand better this setup and I asked the question at https://github.com/facebook/hermes/issues/1518 but didn't get any feedback on it yet. Any information that would help me understand this topic better would be useful.
I did some customizations to Skia C++ code to monitor the reference count of SKData shared pointer and in some cases, that does not go beyond 1 which causes the memory leak in my case (even if I call dispose of SkData and SkImage). I changed the logic to manually delete the pointer SkData when I call dispose from JS side and then the memory came down but this is causing crashes on long run. I am trying to come up with the minimal code to repreoduce this without gesture handlers. I think for this particular problem, I can clearly see a memory leak and SkData objects are not getting deleted when I did profiling on Xcode
I seem to have memory leak issues with skia as well lately. Can't tell if it's related at all, not sure how I can debug this. When I activate skiaFrameProcessor, you can see the memory going up until app crashes. video
all the flag does is activate
const frameProcessor = useSkiaFrameProcessor(frame => {
'worklet'
frame.render(defaultPaint)
}, [paint, defaultPaint, negativeColors])
where: const defaultPaint = Skia.Paint()
Update: Able to fix the issue be downgrading version of: skia from 1.11.5 -> 1.10.2 worklet-core from 1.5.0 -> 1.3.3
@wcandillon This is the smallest possible code I have to show that SkData resources are not GCed. I am debugging the C++ code further to figure out why the SKData pointer is not getting released. Please let me know if you have any clue why this happens
import React, { useEffect, useRef, useState } from 'react';
import { View, StyleSheet, Button } from 'react-native';
import { Canvas, Skia, AlphaType, ColorType, Fill, Image, SkImage, SkData } from '@shopify/react-native-skia';
const tileSize = 512;
const pixels = new Uint8Array(tileSize * tileSize * 4);
pixels.fill(255);
let i = 0;
for (let x = 0; x < tileSize; x++) {
for (let y = 0; y < tileSize; y++) {
pixels[i++] = (x * y) % 255;
}
}
function App(): React.JSX.Element {
const [skiaImage, setSkiaImage] = useState<SkImage>();
const createTile = (tileSize: number) => {
"worklet";
const data = Skia.Data.fromBytes(pixels);
const img = Skia.Image.MakeImage(
{
width: tileSize,
height: tileSize,
alphaType: AlphaType.Opaque,
colorType: ColorType.RGBA_8888,
},
data,
tileSize * 4
);
return img;
}
useEffect(() => {
setSkiaImage(createTile(tileSize) as SkImage);
}
, []);
const regenerateImage = () => {
"worklet";
for (let i = 0; i < 1000; i++) {
skiaImage?.dispose();
setSkiaImage(createTile(tileSize) as SkImage);
}
}
return (
<View style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }}>
<Fill color="pink" />
<Image image={skiaImage as SkImage} x={0} y={0} width={tileSize} height={tileSize} />
</Canvas>
<Button title="Regenerate Image" onPress={regenerateImage} />
</View>
);
};
export default App;
@DImuthuUpe Thanks a lot, I want to look into it. Does using data.dispose help? I do want to want to consolidate the situation here, I think the minimum here would be for SkData to use setExternalMemoryPressure so the gc knows that this is a big object. I'm also not sure if the data is copied from fromBytes, I need to check this as well.
@wcandillon fromBytes copies the data from source buffer to a new one. This is the C++ fragment which does that
auto data =
SkData::MakeWithCopy(buffer.data(runtime), buffer.size(runtime));
However, I further simplified the code to a level where there is NO memory leak based on your suggestion. You can use this and above code as two extreme situations. In order to all smart pointers to clear out, both img.dispose() and data.dispose(); have to be invoked in the right order. (Tweaking C++ code further to find out where the leak is)
import React, { useState } from 'react';
import { View, Button } from 'react-native';
import { AlphaType, ColorType, Skia, } from '@shopify/react-native-skia';
const tileSize = 512;
const pixels = new Uint8Array(tileSize * tileSize * 4);
pixels.fill(255);
let i = 0;
for (let x = 0; x < tileSize; x++) {
for (let y = 0; y < tileSize; y++) {
pixels[i++] = (x * y) % 255;
}
}
function App(): React.JSX.Element {
const regenerateImage = () => {
for (let i = 0; i < 1000; i++) {
const data = Skia.Data.fromBytes(pixels);
const img = Skia.Image.MakeImage(
{
width: tileSize,
height: tileSize,
alphaType: AlphaType.Opaque,
colorType: ColorType.RGBA_8888,
},
data,
tileSize * 4
);
img.dispose();
data.dispose();
}
}
return (
<View style={{ flex: 1 }}>
<Button title="Regenerate Image" onPress={regenerateImage} />
</View>
);
};
export default App;
@wcandillon Finally found the issue. It seems like useState is a pretty bad lifcycle hook to keep track of the SkData and SkImage objects. I had to store the references const dictionaries and alternatively assign the SkImage to a useState when I wanted to render. Then there is no memory leak. This actually is not a rn-skia issue. Please refer to following corrected code. Now the memory is within 300MB range with 100K iterations and I can see some delayed GCs (as you have observed) to clean up memory allocations. I will leave it out to you to give a technical reason for this but for now, we have an alternative mechanism to move forwared with the project
import React, { useEffect, useRef, useState } from 'react';
import { View, StyleSheet, Button } from 'react-native';
import { Canvas, Skia, AlphaType, ColorType, Fill, Image, SkImage, SkData } from '@shopify/react-native-skia';
const tileSize = 512;
const pixels = new Uint8Array(tileSize * tileSize * 4);
pixels.fill(255);
let i = 0;
for (let x = 0; x < tileSize; x++) {
for (let y = 0; y < tileSize; y++) {
pixels[i++] = (x * y) % 255;
}
}
function App(): React.JSX.Element {
const imageCache = {};
const dataCache = {};
const [skiaImage, setSkiaImage] = useState<SkImage>();
const createTile = (tileSize: number) => {
const data = Skia.Data.fromBytes(pixels);
const img = Skia.Image.MakeImage(
{
width: tileSize,
height: tileSize,
alphaType: AlphaType.Opaque,
colorType: ColorType.RGBA_8888,
},
data,
tileSize * 4
);
imageCache[0] = img;
dataCache[0] = data;
}
useEffect(() => {
createTile(tileSize);
setSkiaImage(imageCache[0] as SkImage);
}
, []);
const regenerateImage = () => {
for (let i = 0; i < 1000; i++) {
imageCache[0]?.dispose();
dataCache[0]?.dispose();
createTile(tileSize);
setSkiaImage(imageCache[0] as SkImage);
}
}
return (
<View style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }}>
<Fill color="pink" />
<Imageimage={skiaImage as SkImage} x={0} y={0} width={tileSize} height={tileSize} />
</Canvas>
<Button title="Regenerate Image" onPress={regenerateImage} />
</View>
);
};
export default App;
Thank you for looking into this deeper, I definitely want to consolidate the situation there.
I was also having some weird memory leaks with large images in my iOS app. Thank you for your discoveries DImuthuUpe! This led me to believe something weird is going on with the garbage collection
I switched over to objects to store my images instead of arrays & invoked global.gc() in a few different places where it made sense. Interestingly, I think multiple invocations helped over just invoking once. This seems to have fixed my memory leaks
Hey everyone!
First, I would like to thank everyone for every piece of usefull information we have in this thread.
Inspired on @DImuthuUpe 's last post, I created this component.
const VideoCanvas = ({
data,
width,
height
}: {
data: Uint8Array | null
width: number
height: number
}) => {
const cache = {}
const [skiaImage, setSkiaImage] = useState<SkImage>()
useEffect(() => {
cache[IMAGE_KEY]?.dispose()
cache[DATA_KEY]?.dispose()
const skiaData = Skia.Data.fromBytes(data)
const image = Skia.Image.MakeImage(
{
width,
height,
alphaType: AlphaType.Opaque,
colorType: ColorType.RGBA_8888
},
skiaData,
width * 4
)
cache[IMAGE_KEY] = image
cache[DATA_KEY] = skiaData
setSkiaImage(cache[0])
}, [data])
if (!data || !data.length) {
return (
<View
style={{
width,
height,
backgroundColor: 'red',
justifyContent: 'center',
alignItems: 'center'
}}
>
<ThemedText style={{ color: 'white' }}>No data</ThemedText>
</View>
)
}
if (!skiaImage) {
return (
<View
style={{
width,
height,
backgroundColor: 'red',
justifyContent: 'center',
alignItems: 'center'
}}
>
<ThemedText style={{ color: 'white' }}>
Image creation failed
</ThemedText>
</View>
)
}
return (
<Canvas style={{ width, height, backgroundColor: 'red' }}>
<Image
image={skiaImage}
fit='cover'
x={0}
y={0}
width={width}
height={height}
/>
</Canvas>
)
}
export default VideoCanvas
I observed two things so far:
- It is really stable for something like 10 seconds, I have something like ~230/250mb, but after that it continues to grow until reaching 500mb (in almost 10 seconds)
- When I leave/destroy the view responsible for rendering the video using Skia, the memory used so far still remains!
I'll continue some experiments, and try to grab much info.
I am using version 2.2.1.
We're releasing a new version of Skia right now that dramatically improves memory usage and fixes some memory errors (including the memory pressure patterns seen in this issue). I've test some very compelling examples on this and will tests the ones from this issue as well
Awesome news. Thanks @wcandillon
:tada: This issue has been resolved in version 2.2.15 :tada:
The release is available on:
v2.2.15- GitHub release
Your semantic-release bot :package::rocket:
Still memory leak leading to app crashing on iOS with useSkiaFrameProcessor