Skip to content

Commit 0e01839

Browse files
committed
fix(ios): default image provider memory optimization (Tencent#4478)
1 parent 34ea099 commit 0e01839

1 file changed

Lines changed: 147 additions & 55 deletions

File tree

modules/ios/image/HippyDefaultImageProvider.m

Lines changed: 147 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -106,20 +106,42 @@ - (void)prepareForDisplay:(void (^)(UIImage *_Nullable))completionHandler{
106106

107107

108108
dispatch_async([self prepareQueue], ^{
109-
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc]
110-
initWithSize:theImage.size
111-
format:[UIGraphicsImageRendererFormat preferredFormat]];
112-
UIImage *prepared = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
113-
[theImage drawAtPoint:CGPointZero];
114-
}];
115-
116-
typeof(self) strongSelf = weakSelf;
117-
if(strongSelf){
118-
@synchronized(strongSelf){
119-
if(prepared){
120-
strongSelf->_image = prepared;
109+
@autoreleasepool {
110+
UIImage *prepared = nil;
111+
112+
// Check image dimensions to prevent memory spikes
113+
CGFloat imageWidth = theImage.size.width * theImage.scale;
114+
CGFloat imageHeight = theImage.size.height * theImage.scale;
115+
const CGFloat maxDimension = 4096.0; // Max dimension limit
116+
117+
// Skip rendering for oversized images to avoid memory spikes
118+
if (imageWidth > maxDimension || imageHeight > maxDimension) {
119+
// Use original image for oversized images
120+
prepared = theImage;
121+
} else {
122+
UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat];
123+
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(theImage.CGImage);
124+
BOOL hasAlpha = (alphaInfo != kCGImageAlphaNone &&
125+
alphaInfo != kCGImageAlphaNoneSkipFirst &&
126+
alphaInfo != kCGImageAlphaNoneSkipLast);
127+
format.opaque = !hasAlpha;
128+
129+
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc]
130+
initWithSize:theImage.size
131+
format:format];
132+
prepared = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
133+
[theImage drawAtPoint:CGPointZero];
134+
}];
135+
}
136+
137+
typeof(self) strongSelf = weakSelf;
138+
if(strongSelf){
139+
@synchronized(strongSelf){
140+
if(prepared){
141+
strongSelf->_image = prepared;
142+
}
143+
completionHandler(prepared);
121144
}
122-
completionHandler(prepared);
123145
}
124146
}
125147
});
@@ -133,57 +155,127 @@ - (void)prepareForDisplay:(void (^)(UIImage *_Nullable))completionHandler{
133155
}
134156

135157
- (UIImage *)image {
136-
if (!_image) {
137-
UIImage *tmp;
158+
@synchronized (self) {
159+
if (_image) {
160+
return _image;
161+
}
162+
}
163+
164+
@autoreleasepool {
165+
UIImage *decodedImage = nil;
166+
138167
if (_data) {
139-
CGFloat view_width = _imageViewSize.width;
140-
CGFloat view_height = _imageViewSize.height;
141-
if (_downSample && view_width > 0 && view_height > 0) {
142-
CGFloat scale = self.scale;
143-
NSDictionary *options = @{ (NSString *)kCGImageSourceShouldCache: @(NO) };
144-
CGImageSourceRef ref = CGImageSourceCreateWithData((__bridge CFDataRef)_data, (__bridge CFDictionaryRef)options);
145-
if (ref) {
146-
NSInteger width = 0, height = 0;
147-
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(ref, 0, NULL);
148-
if (properties) {
149-
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
150-
if (val)
151-
CFNumberGetValue(val, kCFNumberLongType, &height);
152-
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
153-
if (val)
154-
CFNumberGetValue(val, kCFNumberLongType, &width);
155-
if (width > (view_width * scale) || height > (view_height * scale)) {
156-
NSInteger maxDimensionInPixels = MAX(view_width, view_height) * scale;
157-
NSDictionary *downsampleOptions = @{
158-
(NSString *)kCGImageSourceCreateThumbnailFromImageAlways: @(YES),
159-
(NSString *)kCGImageSourceShouldCacheImmediately: @(YES),
160-
(NSString *)kCGImageSourceCreateThumbnailWithTransform: @(YES),
161-
(NSString *)kCGImageSourceThumbnailMaxPixelSize: @(maxDimensionInPixels)
162-
};
163-
CGImageRef downsampleImageRef = CGImageSourceCreateThumbnailAtIndex(ref, 0, (__bridge CFDictionaryRef)downsampleOptions);
164-
tmp = [UIImage imageWithCGImage:downsampleImageRef scale:scale orientation:UIImageOrientationUp];
165-
CGImageRelease(downsampleImageRef);
166-
}
167-
CFRelease(properties);
168-
}
169-
CFRelease(ref);
170-
}
171-
}
168+
decodedImage = [self decodeImageFromData];
172169
} else {
173-
tmp = [self imageAtFrame:0];
170+
decodedImage = [self imageAtFrame:0];
174171
}
175-
if(!tmp){
176-
tmp = [UIImage imageWithData:_data scale:self.scale];
172+
173+
// Fallback to simple decoding if needed
174+
if (!decodedImage) {
175+
decodedImage = [UIImage imageWithData:_data scale:self.scale];
177176
}
177+
178178
@synchronized (self) {
179-
if(_image == nil){
180-
_image = tmp;
179+
if (!_image) {
180+
_image = decodedImage;
181181
}
182+
return _image;
182183
}
183184
}
184-
@synchronized (self) {
185-
return _image;
185+
}
186+
187+
- (nullable UIImage *)decodeImageFromData {
188+
if (!_data) {
189+
return nil;
186190
}
191+
192+
// Try downsampling if enabled and view size is available
193+
if (_downSample && _imageViewSize.width > 0 && _imageViewSize.height > 0) {
194+
UIImage *downsampledImage = [self createDownsampledImageFromData];
195+
if (downsampledImage) {
196+
return downsampledImage;
197+
}
198+
}
199+
200+
return nil;
201+
}
202+
203+
- (nullable UIImage *)createDownsampledImageFromData {
204+
CGFloat scale = self.scale;
205+
CGFloat targetWidth = _imageViewSize.width * scale;
206+
CGFloat targetHeight = _imageViewSize.height * scale;
207+
208+
// Create image source without caching
209+
NSDictionary *sourceOptions = @{ (NSString *)kCGImageSourceShouldCache: @(NO) };
210+
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)_data,
211+
(__bridge CFDictionaryRef)sourceOptions);
212+
if (!imageSource) {
213+
return nil;
214+
}
215+
216+
UIImage *result = nil;
217+
CGSize imageSize = [self getImageSizeFromSource:imageSource];
218+
219+
// Only downsample if image is larger than target size
220+
if (imageSize.width > targetWidth || imageSize.height > targetHeight) {
221+
result = [self createThumbnailFromSource:imageSource
222+
targetSize:_imageViewSize
223+
scale:scale];
224+
}
225+
226+
CFRelease(imageSource);
227+
return result;
228+
}
229+
230+
- (CGSize)getImageSizeFromSource:(CGImageSourceRef)imageSource {
231+
CGSize size = CGSizeZero;
232+
233+
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
234+
if (!properties) {
235+
return size;
236+
}
237+
238+
CFTypeRef widthValue = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
239+
CFTypeRef heightValue = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
240+
241+
NSInteger width = 0, height = 0;
242+
if (widthValue) {
243+
CFNumberGetValue(widthValue, kCFNumberLongType, &width);
244+
}
245+
if (heightValue) {
246+
CFNumberGetValue(heightValue, kCFNumberLongType, &height);
247+
}
248+
249+
size = CGSizeMake(width, height);
250+
CFRelease(properties);
251+
252+
return size;
253+
}
254+
255+
- (nullable UIImage *)createThumbnailFromSource:(CGImageSourceRef)imageSource
256+
targetSize:(CGSize)targetSize
257+
scale:(CGFloat)scale {
258+
NSInteger maxDimension = MAX(targetSize.width, targetSize.height) * scale;
259+
260+
NSDictionary *thumbnailOptions = @{
261+
(NSString *)kCGImageSourceCreateThumbnailFromImageAlways: @(YES),
262+
(NSString *)kCGImageSourceShouldCacheImmediately: @(YES),
263+
(NSString *)kCGImageSourceCreateThumbnailWithTransform: @(YES),
264+
(NSString *)kCGImageSourceThumbnailMaxPixelSize: @(maxDimension)
265+
};
266+
267+
CGImageRef thumbnailImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0,
268+
(__bridge CFDictionaryRef)thumbnailOptions);
269+
if (!thumbnailImage) {
270+
return nil;
271+
}
272+
273+
UIImage *result = [UIImage imageWithCGImage:thumbnailImage
274+
scale:scale
275+
orientation:UIImageOrientationUp];
276+
CGImageRelease(thumbnailImage);
277+
278+
return result;
187279
}
188280

189281
- (UIImage *)imageAtFrame:(NSUInteger)index {

0 commit comments

Comments
 (0)