mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-26 11:17:04 +08:00
perf(ios): UIImage memory leaks (#9783)
This commit is contained in:

committed by
Nathan Walker

parent
f37b0160ed
commit
988f372788
@ -17,6 +17,7 @@ export class AccessibilityModel extends Observable {
|
|||||||
accessibilityLiveRegions = AccessibilityLiveRegion;
|
accessibilityLiveRegions = AccessibilityLiveRegion;
|
||||||
accessibilityRole = AccessibilityRole;
|
accessibilityRole = AccessibilityRole;
|
||||||
accessibilityState = AccessibilityState;
|
accessibilityState = AccessibilityState;
|
||||||
|
largeImageSrc = 'https://i.picsum.photos/id/669/5000/5000.jpg?hmac=VlpchW0ODhflKm0SKOYQrc8qysLWbqKmDS1MGT9apAc';
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -26,6 +27,11 @@ export class AccessibilityModel extends Observable {
|
|||||||
const checked = (args.object as Switch).checked;
|
const checked = (args.object as Switch).checked;
|
||||||
console.log(checked);
|
console.log(checked);
|
||||||
this.set('switchCheckedText', `${this.labelText} ${checked}`);
|
this.set('switchCheckedText', `${this.labelText} ${checked}`);
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
this.set('largeImageSrc', checked ?
|
||||||
|
'https://i.picsum.photos/id/669/5000/5000.jpg?hmac=VlpchW0ODhflKm0SKOYQrc8qysLWbqKmDS1MGT9apAc' :
|
||||||
|
'https://i.picsum.photos/id/684/5000/5000.jpg?hmac=loiXO_OQ-y86XY_hc7p3qJdY39fSd9CuDM0iA_--P4Q');
|
||||||
}
|
}
|
||||||
|
|
||||||
openModal() {
|
openModal() {
|
||||||
|
@ -13,12 +13,15 @@
|
|||||||
<Image src="res://icon" width="50" class="view-item a11y" accessibilityLabel="Image with explicit attribute role" accessibilityRole="{{accessibilityRole.Image}}" />
|
<Image src="res://icon" width="50" class="view-item a11y" accessibilityLabel="Image with explicit attribute role" accessibilityRole="{{accessibilityRole.Image}}" />
|
||||||
<Image src="res://icon" width="50" class="view-item a11y a11y-role-image" accessibilityLabel="Image with css defined role" />
|
<Image src="res://icon" width="50" class="view-item a11y a11y-role-image" accessibilityLabel="Image with css defined role" />
|
||||||
|
|
||||||
|
|
||||||
|
<Image src="{{ largeImageSrc }}" width="50" class="view-item a11y a11y-role-image" accessibilityLabel="Image with css defined role" />
|
||||||
|
|
||||||
<Switch checked="true" class="view-item a11y" accessibilityLabel="Switch with attribute state" accessibilityState="{{accessibilityState.Checked}}" checkedChange="{{checkedChange}}" />
|
<Switch checked="true" class="view-item a11y" accessibilityLabel="Switch with attribute state" accessibilityState="{{accessibilityState.Checked}}" checkedChange="{{checkedChange}}" />
|
||||||
<Switch checked="true" class="view-item a11y a11y-state-checked" accessibilityLabel="Switch with css state" checkedChange="{{checkedChange}}" />
|
<Switch checked="true" class="view-item a11y a11y-state-checked" accessibilityLabel="Switch with css state" checkedChange="{{checkedChange}}" />
|
||||||
|
|
||||||
<TextView hint="TextView" text="{{switchCheckedText}}" class="view-item a11y" accessibilityLabel="TestView with a value" accessibilityLiveRegion="{{accessibilityLiveRegions.Polite}}"/>
|
<TextView hint="TextView" text="{{switchCheckedText}}" class="view-item a11y" accessibilityLabel="TestView with a value" accessibilityLiveRegion="{{accessibilityLiveRegions.Polite}}" />
|
||||||
<TextField hint="TextField" class="view-item a11y" accessibilityLabel="Plain jane TextField" accessibilityHint="Tell us your real name Jane"/>
|
<TextField hint="TextField" class="view-item a11y" accessibilityLabel="Plain jane TextField" accessibilityHint="Tell us your real name Jane" />
|
||||||
<TextView hint="TextView" class="view-item a11y" accessibilityLabel="Nice TextView" accessibilityHint="Tell us about yourself Jane"/>
|
<TextView hint="TextView" class="view-item a11y" accessibilityLabel="Nice TextView" accessibilityHint="Tell us about yourself Jane" />
|
||||||
<GridLayout rows="25" columns="*" class="view-item" accessibilityLabel="No can go GridLayout" accessibilityHint="A grid that will not get bigger when increasing accessible text size">
|
<GridLayout rows="25" columns="*" class="view-item" accessibilityLabel="No can go GridLayout" accessibilityHint="A grid that will not get bigger when increasing accessible text size">
|
||||||
<Label text="IN-Accessible Grid" class="view-item text-center" />
|
<Label text="IN-Accessible Grid" class="view-item text-center" />
|
||||||
</GridLayout>
|
</GridLayout>
|
||||||
@ -28,7 +31,7 @@
|
|||||||
<Label rowSpan="2" col="1" text="Hi" />
|
<Label rowSpan="2" col="1" text="Hi" />
|
||||||
</GridLayout>
|
</GridLayout>
|
||||||
<Button text="Open Modal" class="view-item" tap="{{openModal}}" />
|
<Button text="Open Modal" class="view-item" tap="{{openModal}}" />
|
||||||
<Slider value="10" minValue="0" maxValue="100" class="view-item a11y" accessibilityLabel="Slider" accessibilityHint="A smooth slider" accessibilityValue="10"/>
|
<Slider value="10" minValue="0" maxValue="100" class="view-item a11y" accessibilityLabel="Slider" accessibilityHint="A smooth slider" accessibilityValue="10" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</GridLayout>
|
</GridLayout>
|
||||||
|
@ -28,10 +28,16 @@ export abstract class ImageBase extends View implements ImageDefinition {
|
|||||||
this.style.tintColor = value;
|
this.style.tintColor = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public disposeImageSource() {
|
||||||
|
// override in subclass
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public _createImageSourceFromSrc(value: string | ImageSource | ImageAsset): void {
|
public _createImageSourceFromSrc(value: string | ImageSource | ImageAsset): void {
|
||||||
|
this.disposeImageSource();
|
||||||
|
|
||||||
const originalValue = value;
|
const originalValue = value;
|
||||||
const sync = this.loadMode === 'sync';
|
const sync = this.loadMode === 'sync';
|
||||||
if (typeof value === 'string' || value instanceof String) {
|
if (typeof value === 'string' || value instanceof String) {
|
||||||
|
@ -2,7 +2,7 @@ import { ImageBase, stretchProperty, imageSourceProperty, tintColorProperty, src
|
|||||||
import { ImageSource } from '../../image-source';
|
import { ImageSource } from '../../image-source';
|
||||||
import { Color } from '../../color';
|
import { Color } from '../../color';
|
||||||
import { Trace } from '../../trace';
|
import { Trace } from '../../trace';
|
||||||
import { layout } from '../../utils';
|
import { layout, queueGC } from '../../utils';
|
||||||
|
|
||||||
export * from './image-common';
|
export * from './image-common';
|
||||||
|
|
||||||
@ -24,21 +24,28 @@ export class Image extends ImageBase {
|
|||||||
this._setNativeClipToBounds();
|
this._setNativeClipToBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public disposeNativeView(): void {
|
public disposeImageSource() {
|
||||||
super.disposeNativeView();
|
if (this.nativeViewProtected?.image === this.imageSource?.ios) {
|
||||||
|
this.nativeViewProtected.image = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.imageSource?.ios) {
|
if (this.imageSource?.ios) {
|
||||||
this.imageSource.ios = null;
|
this.imageSource.ios = null;
|
||||||
// causes crash currently:
|
|
||||||
// release the native UIImage
|
|
||||||
// CFRelease(this.imageSource.ios);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageSource = null;
|
this.imageSource = null;
|
||||||
|
|
||||||
|
queueGC();
|
||||||
|
}
|
||||||
|
|
||||||
|
public disposeNativeView(): void {
|
||||||
|
super.disposeNativeView();
|
||||||
|
|
||||||
if (this.nativeViewProtected?.image) {
|
if (this.nativeViewProtected?.image) {
|
||||||
this.nativeViewProtected.image = null;
|
this.nativeViewProtected.image = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.disposeImageSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setTintColor(value: Color) {
|
private setTintColor(value: Color) {
|
||||||
@ -46,15 +53,23 @@ export class Image extends ImageBase {
|
|||||||
if (value && this.nativeViewProtected.image && !this._templateImageWasCreated) {
|
if (value && this.nativeViewProtected.image && !this._templateImageWasCreated) {
|
||||||
this.nativeViewProtected.image = this.nativeViewProtected.image.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
|
this.nativeViewProtected.image = this.nativeViewProtected.image.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
|
||||||
this._templateImageWasCreated = true;
|
this._templateImageWasCreated = true;
|
||||||
|
queueGC();
|
||||||
} else if (!value && this.nativeViewProtected.image && this._templateImageWasCreated) {
|
} else if (!value && this.nativeViewProtected.image && this._templateImageWasCreated) {
|
||||||
this._templateImageWasCreated = false;
|
this._templateImageWasCreated = false;
|
||||||
this.nativeViewProtected.image = this.nativeViewProtected.image.imageWithRenderingMode(UIImageRenderingMode.Automatic);
|
this.nativeViewProtected.image = this.nativeViewProtected.image.imageWithRenderingMode(UIImageRenderingMode.Automatic);
|
||||||
|
queueGC();
|
||||||
}
|
}
|
||||||
this.nativeViewProtected.tintColor = value ? value.ios : null;
|
this.nativeViewProtected.tintColor = value ? value.ios : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public _setNativeImage(nativeImage: UIImage) {
|
public _setNativeImage(nativeImage: UIImage) {
|
||||||
|
if (this.nativeViewProtected?.image) {
|
||||||
|
this.nativeViewProtected.image = null;
|
||||||
|
|
||||||
|
queueGC();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.nativeViewProtected) {
|
if (this.nativeViewProtected) {
|
||||||
this.nativeViewProtected.image = nativeImage;
|
this.nativeViewProtected.image = nativeImage;
|
||||||
}
|
}
|
||||||
@ -169,6 +184,10 @@ export class Image extends ImageBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[imageSourceProperty.setNative](value: ImageSource) {
|
[imageSourceProperty.setNative](value: ImageSource) {
|
||||||
|
if (value !== this.imageSource) {
|
||||||
|
this.disposeImageSource();
|
||||||
|
}
|
||||||
|
|
||||||
this._setNativeImage(value ? value.ios : null);
|
this._setNativeImage(value ? value.ios : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
packages/core/utils/index.d.ts
vendored
5
packages/core/utils/index.d.ts
vendored
@ -187,6 +187,11 @@ export namespace ad {
|
|||||||
*/
|
*/
|
||||||
export function GC();
|
export function GC();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility function that queues a garbage collection, subseqent calls will be throttled and only one gc will be executed.
|
||||||
|
*/
|
||||||
|
export function queueGC();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the reference to the wrapped native object
|
* Releases the reference to the wrapped native object
|
||||||
* @param object The Java/Objective-C object to release.
|
* @param object The Java/Objective-C object to release.
|
||||||
|
@ -3,6 +3,8 @@ import { dispatchToMainThread, isMainThread } from './mainthread-helper';
|
|||||||
import { sanitizeModuleName } from '../ui/builder/module-name-sanitizer';
|
import { sanitizeModuleName } from '../ui/builder/module-name-sanitizer';
|
||||||
import * as layout from './layout-helper';
|
import * as layout from './layout-helper';
|
||||||
|
|
||||||
|
import { GC } from './index';
|
||||||
|
|
||||||
export { layout };
|
export { layout };
|
||||||
export * from './mainthread-helper';
|
export * from './mainthread-helper';
|
||||||
export * from './macrotask-scheduler';
|
export * from './macrotask-scheduler';
|
||||||
@ -129,3 +131,17 @@ export function mainThreadify(func: Function): (...args: any[]) => void {
|
|||||||
executeOnMainThread(() => func.apply(this, argsToPass));
|
executeOnMainThread(() => func.apply(this, argsToPass));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasQueuedGC = false;
|
||||||
|
export function queueGC() {
|
||||||
|
if (hasQueuedGC) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasQueuedGC = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
hasQueuedGC = false;
|
||||||
|
GC();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user