Skip to content

Commit 3ec2029

Browse files
committed
fix(image): ios added ImagePipeline.iosComplexCacheEviction
When enabled we remove all cached images with different transforms. It is a tricky implementation but for now it should work in most cases
1 parent 3bbc852 commit 3ec2029

File tree

3 files changed

+55
-22
lines changed

3 files changed

+55
-22
lines changed

src/image-colorfilter/index.ios.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ function filterFromMatrix(matrix: number[], ciFilter:CIFilter = CIFilter.filterW
2222
ciFilter.setName(JSON.stringify(matrix));
2323
}
2424
registerPluginGetContextFromOptions((context, transformers, options: Partial<Img>) => {
25-
console.log('colorFilter GetContextFromOptions')
2625
const ciFilter = options.mCIFilter ?? (options.colorMatrix? filterFromMatrix(options.colorMatrix) : undefined);
2726
if (ciFilter) {
2827
transformers.push(SDImageFilterTransformer.transformerWithFilter(ciFilter));

src/image/index.d.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,12 @@ export interface AnimatedImage {
349349
* The entry point for the image pipeline..
350350
*/
351351
export class ImagePipeline {
352+
/**
353+
* iOS: set this to true to enable complex cache handling
354+
* this is necessary when you use colorMatrix, decodeWidth,...
355+
* and when you change images on drive requiring cache image eviction
356+
*/
357+
static iosComplexCacheEviction: boolean;
352358
/**
353359
* Returns whether the image is stored in the bitmap memory cache.
354360
*/
@@ -455,6 +461,6 @@ export interface ImagePipelineConfigSetting {
455461
}
456462
export const ImageViewTraceCategory;
457463

458-
export type GetContextFromOptionsCallback = (context: NSDictionary<string, any>, transformers:any[], options: Partial<Img>)=>void
464+
export type GetContextFromOptionsCallback = (context: NSDictionary<string, any>, transformers: any[], options: Partial<Img>) => void;
459465

460-
declare function registerPluginGetContextFromOptions(callback: GetContextFromOptionsCallback); // iOS only for plugins
466+
declare function registerPluginGetContextFromOptions(callback: GetContextFromOptionsCallback); // iOS only for plugins

src/image/index.ios.ts

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './index-common';
2-
import { Color, ImageAsset, ImageSource, Screen, Trace, Utils, knownFolders, path } from '@nativescript/core';
2+
import { ApplicationSettings, Color, ImageAsset, ImageSource, Screen, Trace, Utils, knownFolders, path } from '@nativescript/core';
3+
import { debounce } from '@nativescript/core/utils';
34
import { layout } from '@nativescript/core/utils/layout-helper';
45
import { isString } from '@nativescript/core/utils/types';
56
import {
@@ -106,9 +107,9 @@ export function initialize(config?: ImagePipelineConfigSetting): void {
106107
}
107108
export function shutDown(): void {}
108109

109-
let pluginsGetContextFromOptions = new Set<GetContextFromOptionsCallback>();
110+
const pluginsGetContextFromOptions = new Set<GetContextFromOptionsCallback>();
110111
export function registerPluginGetContextFromOptions(callback: GetContextFromOptionsCallback) {
111-
pluginsGetContextFromOptions.add(callback)
112+
pluginsGetContextFromOptions.add(callback);
112113
}
113114
function getContextFromOptions(options: Partial<Img>) {
114115
const context: NSDictionary<string, any> = NSMutableDictionary.dictionary();
@@ -141,14 +142,30 @@ function getContextFromOptions(options: Partial<Img>) {
141142
)
142143
);
143144
}
145+
pluginsGetContextFromOptions.forEach((c) => c(context, transformers, options));
146+
144147
if (transformers.length > 0) {
145148
context.setValueForKey(SDImagePipelineTransformer.transformerWithTransformers(transformers), SDWebImageContextImageTransformer);
146149
}
147-
pluginsGetContextFromOptions.forEach(c=>c(context, transformers, options));
148150
return context;
149151
}
150152

153+
// This is not the best solution as their might be a lot of corner cases
154+
// for example if an image is removed from cache without going through ImagePipeline
155+
// we wont know it and the cacheKeyMap will grow
156+
// but i dont see a better way right now
157+
const CACHE_KEYS_SETTING_KEY = 'NS_ui_image_cache_keys';
158+
let cacheKeyMap = JSON.parse(ApplicationSettings.getString(CACHE_KEYS_SETTING_KEY, '{}'));
159+
160+
const saveCacheKeys = debounce(() => ApplicationSettings.setString(CACHE_KEYS_SETTING_KEY, JSON.stringify(cacheKeyMap)), 500);
161+
function registerCacheKey(cacheKey: string, uri: any) {
162+
const set = new Set(cacheKeyMap[uri] || []);
163+
set.add(cacheKey);
164+
cacheKeyMap[uri] = [...set];
165+
saveCacheKeys();
166+
}
151167
export class ImagePipeline {
168+
static iosComplexCacheEviction = false;
152169
private mIos: SDImageCache = SDImageCache.sharedImageCache;
153170
constructor() {}
154171

@@ -166,22 +183,32 @@ export class ImagePipeline {
166183
}
167184

168185
evictFromMemoryCache(key: string): void {
169-
this.mIos.removeImageFromMemoryForKey(key);
170-
}
171-
172-
async evictFromDiskCache(key: string) {
173-
return new Promise<void>((resolve) => {
174-
this.mIos.removeImageForKeyCacheTypeCompletion(key, SDImageCacheType.Disk, resolve);
186+
const cachekKeys = (cacheKeyMap[key] || []).concat([key]);
187+
cachekKeys.forEach((k) => {
188+
this.mIos.removeImageFromMemoryForKey(k);
175189
});
176190
}
177191

178-
async evictFromCache(key: string) {
179-
return new Promise<void>((resolve) => {
180-
this.mIos.removeImageForKeyCacheTypeCompletion(key, SDImageCacheType.All, resolve);
181-
});
192+
async evictFromDiskCache(key: string) {
193+
return this.evictFromCache(key, SDImageCacheType.Disk);
194+
}
195+
196+
async evictFromCache(key: string, type = SDImageCacheType.All) {
197+
const cachekKeys = (cacheKeyMap[key] || []).concat([key]);
198+
delete cacheKeyMap[key];
199+
return Promise.all(
200+
cachekKeys.map(
201+
(k) =>
202+
new Promise<void>((resolve) => {
203+
this.mIos.removeImageForKeyCacheTypeCompletion(k, type, resolve);
204+
})
205+
)
206+
);
182207
}
183208

184209
clearCaches() {
210+
cacheKeyMap = {};
211+
ApplicationSettings.remove(CACHE_KEYS_SETTING_KEY);
185212
this.mIos.clearMemory();
186213
this.mIos.clearDiskOnCompletion(null);
187214
}
@@ -347,10 +374,9 @@ export class Img extends ImageBase {
347374
const src = this.src;
348375
const srcType = typeof src;
349376
if (src && (srcType === 'string' || src instanceof ImageAsset)) {
350-
const cachekKey = this.mCacheKey || getUri(src as string | ImageAsset).absoluteString;
351377
// const isInCache = imagePipeLine.isInBitmapMemoryCache(cachekKey);
352378
// if (isInCache) {
353-
await imagePipeLine.evictFromCache(cachekKey);
379+
await imagePipeLine.evictFromCache(getUri(src as string | ImageAsset).absoluteString);
354380
// }
355381
}
356382
// this.src = null;
@@ -539,11 +565,14 @@ export class Img extends ImageBase {
539565
}
540566

541567
this.mCacheKey = SDWebImageManager.sharedManager.cacheKeyForURLContext(uri, context);
568+
if (ImagePipeline.iosComplexCacheEviction) {
569+
registerCacheKey(this.mCacheKey, uri);
570+
}
542571
if (this.showProgressBar) {
543572
try {
544573
if (this.progressBarColor) {
545574
const indicator = new SDWebImageActivityIndicator();
546-
indicator.indicatorView.color = this.progressBarColor.ios;
575+
indicator.indicatorView.color = (this.progressBarColor as Color).ios;
547576
this.nativeImageViewProtected.sd_imageIndicator = indicator;
548577
} else {
549578
this.nativeImageViewProtected.sd_imageIndicator = SDWebImageActivityIndicator.grayIndicator;
@@ -590,8 +619,7 @@ export class Img extends ImageBase {
590619
}
591620

592621
@needRequestImage
593-
[headersProperty.setNative](value) {
594-
}
622+
[headersProperty.setNative](value) {}
595623

596624
[failureImageUriProperty.setNative]() {
597625
// this.updateHierarchy();

0 commit comments

Comments
 (0)